├── .github
└── workflows
│ └── version-and-release.yml
├── .gitignore
├── Dockerfile
├── README.md
├── Taskfile.yaml
├── docker-compose.yml
├── go.mod
├── go.sum
├── gologger
└── gologger.go
├── http_server
├── custom_context.go
├── http_server.go
├── http_server_test.go
└── query.go
├── k8s
└── test
│ └── deployment.yml
├── ksd
└── ksd.go
├── main.go
├── main_test.go
├── pg
├── pg.go
├── pool.go
├── tx.go
├── tx_manager.go
└── tx_test.go
├── red
└── red.go
├── test.http
└── utils
├── env.go
├── errors.go
└── utils.go
/.github/workflows/version-and-release.yml:
--------------------------------------------------------------------------------
1 | # Credit to @Hades32 for making most of this
2 | name: Build and Release
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | env:
9 | REGISTRY: ghcr.io
10 | IMAGE_NAME: sqlgateway
11 |
12 | jobs:
13 | build-and-push:
14 | permissions:
15 | contents: write
16 | packages: write
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v2
20 | with:
21 | fetch-depth: '0'
22 |
23 | - uses: actions/setup-go@v3
24 | with:
25 | go-version: '>=1.18.0'
26 |
27 | - name: Bump version and push tag
28 | uses: anothrNick/github-tag-action@1.36.0
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 | WITH_V: true
32 | DEFAULT_BUMP: patch
33 |
34 | # - name: Run GoReleaser - Build and make github release
35 | # uses: goreleaser/goreleaser-action@v2
36 | # with:
37 | # args: release --rm-dist
38 | # env:
39 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 |
41 | - name: Log in to the Container registry
42 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
43 | with:
44 | registry: ${{ env.REGISTRY }}
45 | username: ${{ github.actor }}
46 | password: ${{ secrets.GITHUB_TOKEN }}
47 |
48 | - name: 'Get Previous tag'
49 | id: previoustag
50 | uses: "WyriHaximus/github-action-get-previous-tag@v1"
51 |
52 | - name: Set up QEMU
53 | uses: docker/setup-qemu-action@v1
54 |
55 | - name: Set up Docker Buildx
56 | id: buildx
57 | uses: docker/setup-buildx-action@v1
58 |
59 | - name: Build and push Docker image
60 | uses: docker/build-push-action@v2
61 | with:
62 | context: .
63 | push: true
64 | tags: ${{ env.REGISTRY }}/danthegoodman1/${{ env.IMAGE_NAME }}:latest,${{ env.REGISTRY }}/danthegoodman1/${{ env.IMAGE_NAME }}:${{ steps.previoustag.outputs.tag }}
65 | platforms: linux/amd64,linux/arm64
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.19.3 as build
2 |
3 | WORKDIR /app
4 |
5 | COPY go.* /app/
6 |
7 | RUN --mount=type=cache,target=/go/pkg/mod \
8 | --mount=type=cache,target=/root/.cache/go-build \
9 | --mount=type=ssh \
10 | go mod download
11 |
12 | COPY . .
13 |
14 | RUN --mount=type=cache,target=/go/pkg/mod \
15 | --mount=type=cache,target=/root/.cache/go-build \
16 | go build $GO_ARGS -o /app/SQLGateway
17 |
18 | # Need glibc
19 | FROM gcr.io/distroless/base
20 |
21 | ENTRYPOINT ["/app/SQLGateway"]
22 | COPY --from=build /app/SQLGateway /app/
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SQLGateway
2 |
3 |
4 | Access your SQL database over HTTP like it’s a SQL database but with superpowers. An edge function's best friend.
5 |
6 | **Superpowers include:**
7 |
8 | - HTTP access for SQL databases enable WASM-based runtimes to use TCP-connected DBs
9 | - Connection pooling protects from reconnects, wasted idle connections, and bursts of load
10 | - Automatic query and transaction tracing
11 | - Caching capabilities
12 |
13 | _Currently only the PSQL protocol is supported. Additional protocol support (like MySQL) is on the roadmap._
14 |
15 | - [Quick Start](#quick-start)
16 | - [Why This Exists](#why-this-exists)
17 | - [Querying and Transactions](#querying-and-transactions)
18 | - [Automatic query and transaction tracing](#automatic-query-and-transaction-tracing)
19 | - [Caching (Coming Soon)](#caching-coming-soon)
20 | - [Connection Pooling](#connection-pooling)
21 | - [Database Throttling Under Load](#database-throttling-under-load)
22 | - [API](#api)
23 | - [GET /hc](#get-hc)
24 | - [POST /psql/query](#post-psqlquery)
25 | - [/psql/begin](#psqlbegin)
26 | - [/psql/commit](#psqlcommit)
27 | - [/psql/rollback](#psqlrollback)
28 | - [Error handling](#error-handling)
29 | - [Configuration](#configuration)
30 | - [Auth](#auth)
31 | - [Clustered vs. Single Node](#clustered-vs-single-node)
32 | - [Transactions](#transactions)
33 | - [Running distributed tests](#running-distributed-tests)
34 |
35 | ## Quick Start
36 |
37 |
38 | Pull this repo:
39 |
40 | ```
41 | git clone https://github.com/danthegoodman1/SQLGateway && cd SQLGateway
42 | ```
43 |
44 | Run the `docker-compose.yml` file:
45 |
46 | ```
47 | docker compose up
48 | ```
49 |
50 | Then in another terminal, run:
51 |
52 | ```
53 | curl --location --request POST 'http://localhost:8080/psql/query' \
54 | --header 'Content-Type: application/json' \
55 | --data-raw '{
56 | "Queries": [
57 | {
58 | "Statement": "SELECT 1 as num_one, NOW() as current_time"
59 | }
60 | ]
61 | }'
62 | ```
63 |
64 | You should get the following back (with a different time):
65 |
66 | ```
67 | {"Queries":[{"Columns":["num_one","current_time"],"Rows":[[1,"2022-11-27T19:20:13.030114Z"]],"TimeNS":958400}]}
68 | ```
69 |
70 | ## Why This Exists
71 |
72 | I wanted to use Cloudflare Workers, but also the Postgres ecosystem (specifically CockroachDB Serverless).
73 |
74 | The idea was to keep the HTTP layer out of the way and make it feel like you are talking to a normal SQL database.
75 |
76 | Now we can connect the two worlds of WASM-runtimes and SQL databases without vendor lock-in!
77 |
78 | Some WASM runtimes that can now use SQL databases:
79 |
80 | - Cloudflare Workers
81 | - Vercel Edge Functions
82 | - Fastly Compute@Edge
83 | - Netlify Functions _note: [this](https://wasmedge.org/book/en/write_wasm/js/networking.html#tcp-client) seems to indicate that TCP connections may be supported, since they (at least used to) use WasmEdge. I have not bothered testing however :P_
84 |
85 | Some Databases that WASM runtimes can now use:
86 |
87 | - AWS RDS & Aurora
88 | - GCP Cloud SQL
89 | - CockroachDB Dedicated & Serverless
90 | - DigitalOcean managed databases
91 | - UpCloud Managed Databases
92 |
93 | ### Querying and Transactions
94 |
95 | Send single queries, or send an array of queries to run atomically in a transaction.
96 |
97 | Start a transaction and go back and forth between the DB and your code just like normal. The nodes in the cluster will automatically route transaction queries to the correct node (coordinated through Redis). Abandoned transactions will be garbage collected.
98 |
99 | ### Automatic query and transaction tracing
100 |
101 | Metric logs emitted on the performance of individual queries, as well as entire transactions. Build dashboards and create alerts to find slowdowns and hot-spots in your code.
102 |
103 | Coming soon (maybe?): Alerting and dashboards (for now just use some logging provider)
104 |
105 | ### Caching (Coming Soon)
106 |
107 | Specify SELECTs that don’t need to be consistent you can have them cache and TTL with stale-while-revalidate support.
108 |
109 | ### Connection Pooling
110 |
111 | Prevent constant session creation from creating unnecessary load on the DB, and burst execution environments from holding idle connections that won't be used again.
112 |
113 | Use HTTP Keep-Alive to keep connections warm for Lambda-like environments, but don’t risk overloading the DB with new connections or leaving tons of resource-intensive DB sessions idle.
114 |
115 | ### Database Throttling Under Load
116 |
117 | With a finite number of pool connections, you prevent uncapped load from hitting your database directly.
118 |
119 | ## API
120 |
121 | ### GET /hc
122 |
123 | Health check endpoint, only guarantees that the HTTP server is running.
124 |
125 | ### POST /psql/query
126 |
127 | Request Body:
128 |
129 | _`*` indicates optional_
130 | ```
131 | {
132 | Queries: []{
133 | Statement: string
134 | Params: []any
135 | Exec: *bool // if provided, then no `Rows` or `Columns` will be returned for this query.
136 | TxKey: *string
137 | }
138 |
139 | TxID: *string
140 | }
141 | ```
142 |
143 | Examples:
144 | ```json
145 | {
146 | "Queries": [
147 | {
148 | "Statement": "SELECT $1::INT8 as a_number",
149 | "Params": [
150 | 42
151 | ]
152 | }
153 | ]
154 | }
155 | ```
156 | ```json
157 | {
158 | "Queries": [
159 | {
160 | "Statement": "CREATE TABLE test_table IF NOT EXISTS ( id TEXT NOT NULL, val TEXT NOT NULL, PRIMARY KEY(id) )",
161 | "Exec": true
162 | }
163 | ]
164 | }
165 | ```
166 |
167 | **Note:** Casting is probably required for parameters as due to the primitive type selection the SQL cannot always interpret which SQL type a JSON property should use.
168 |
169 | If given a single query, it will be run directly on the connection.
170 |
171 | If given multiple items, they will be run within the same transaction. You will receive the results of all that succeed,
172 | however if a single query fails then the entire transaction will fail, and all queries will remain un-applied regardless
173 | of whether there were rows returned. Rows will be returned for the successful queries of a failing transaction.
174 |
175 | If a `TxID` is provided, then it will be run within a transaction, proxying if required.
176 |
177 | DO NOT CALL `COMMIT` OR `ROLLBACK` through here, that should be handled via the respective endpoints, or functions within the client libraries.
178 |
179 | Response Body:
180 |
181 | ```
182 | {
183 | Queries []{
184 | Columns: []any
185 | Rows: [][]any
186 | Error: *string
187 | TimeNS: *int64
188 | }
189 |
190 | // Whether this was proxied to a remote node
191 | Remote: bool
192 | }
193 | ```
194 |
195 | Any query errors that occur will be included in the response body, rather than failing the request.
196 |
197 | ### /psql/begin
198 |
199 | Starts a new transaction.
200 |
201 | Request Body:
202 |
203 | ```
204 | {
205 | TxTimeoutSec: *int64 // sets the garbage collection timeout, default `30`
206 | }
207 | ```
208 |
209 | Returns the transaction ID that must be carried through subsequent requests.
210 |
211 | Response Body:
212 |
213 | ```
214 | {
215 | TxID: string
216 | }
217 | ```
218 |
219 | ### /psql/commit
220 |
221 | Commits an existing transaction. Returns status `200` and no content if successful.
222 |
223 | Request Body:
224 |
225 | ```
226 | {
227 | TxID: string
228 | }
229 | ```
230 |
231 | ### /psql/rollback
232 |
233 | Rolls back an existing transaction. Returns status `200` and no content if successful.
234 |
235 | Request Body:
236 |
237 | ```
238 | {
239 | TxID: string
240 | }
241 | ```
242 |
243 | ### Error handling
244 |
245 | All processing errors (not query errors) will return a 4XX/5XX error code, and as a `text/plain` response body.
246 |
247 | ## Configuration
248 |
249 | Configuration is done through environment variables
250 |
251 | | Env Var | Description | Required? | Default |
252 | |--------------------|----------------------------------------------------------------------------------------------------------------------------|----------------------------|---------|
253 | | `PG_DSN` | PSQL wire protocol DSN. Used to connect to DB | Yes | |
254 | | `PG_POOL_CONNS` | Number of pool connections to acquire | No | `2` |
255 | | `REDIS_ADDR` | Redis Address. Currently used in non-cluster mode (standard client).
If omitted then clustering features are disabled. | No | |
256 | | `REDIS_PASSWORD` | Redis connection password | No | |
257 | | `REDIS_POOL_CONNS` | Number of pool connections to Redis. | No | `2` |
258 | | `V_NAMESPACE` | Virtual namespace for Redis. Sets the key prefix for Service discovery. | Yes (WIP, so No currently) | |
259 | | `POD_URL` | Direct URL that this pod/node can be reached at.
Replaces `POD_NAME` and `POD_BASE_DOMAIN` if exists. | Yes (conditional) | |
260 | | `POD_NAME` | Name of the node/pod (k8s semantics).
Pod can be reached at {POD_NAME}{POD_BASE_DOMAIN} | Yes (conditional) | |
261 | | `POD_BASE_DOMAIN` | Base domain of the node/pod (k8s semantics).
Pod can be reached at {POD_NAME}{POD_BASE_DOMAIN} | Yes (conditional) | |
262 | | `HTTP_PORT` | HTTP port to run the HTTP(2) server on | No | `8080` |
263 | | `POD_HTTPS` | Indicates whether the pods should use HTTPS to contact each other.
Set to `1` if they should use HTTPS. | No | |
264 | | `TRACES` | Indicates whether query trace information should be included in log contexts.
Set to `1` if they should be. | No | |
265 | | `DEBUG` | Indicates whether the debug log level should be enabled.
Set to `1` to enable. | No | |
266 | | `PRETTY` | Indicates whether pretty logs should be printed.
Set to `1` to enable. | | |
267 | | `AUTH_USER` | Sets the Basic Auth username required to connect. Requires that `AUTH_PASS` be set as well | Yes (conditional) | |
268 | | `AUTH_PASS` | Sets the Basic Auth password required to connect. Requires that `AUTH_USER` be set as well | Yes (conditional) | |
269 |
270 | ## Auth
271 |
272 | SQLGateway optionally supports Basic Auth in the style of `http(s)://USER:PASS@your.domain.tld`, just like a DB DSN.
273 |
274 | To configure a username and password, set the env vars `AUTH_USER` and `AUTH_PASS`, then access with `http://{AUTH_USER}:{AUTH_PASS}@yourdomain`
275 |
276 | ## Performance and Overhead
277 |
278 | With some light testing on my laptop (2019 16" MBP, 8 core 64GB ram) selecting 10,001 rows directly from `pgx` without processing takes ~8-9ms, while requesting that same query through SQLGateway takes ~12-13ms.
279 |
280 | SQLGateway currently uses the native JSON package, which is known to be very slow. The binding overhead can be optimized by >10x with a more efficient JSON package for binding.
281 |
282 | HTTP overhead is ~80us per request, a bit more if you add in Basic Auth.
283 |
284 | ## Clustered vs. Single Node
285 |
286 | SQLGateway can either be run in a cluster, or as a single node.
287 |
288 | If running as a single node, ensure to omit the `REDIS_ADDR` env var.
289 |
290 | When running in clustered mode (`REDIS_ADDR` env var present), it will require that a connection to Redis can be established.
291 |
292 | When transactions are not found locally, a lookup to Redis will be attempted. If the transaction is found on a remote pod,
293 | the request will be proxied to the remote pod.
294 |
295 | Redis Cluster mode support and etcd support are on the roadmap.
296 |
297 | ## Transactions
298 |
299 | Transactions (and query requests) have a default timeout of 30 seconds.
300 |
301 | When any query in a transaction fails, the transaction is automatically rolled back and the pool connection released, meaning that the client that errors is not responsible for doing so.
302 |
303 | If a transaction times out then it will also automatically roll back and release the pool connection.
304 |
305 | If a pod crashes while it has a transaction, then the transaction will be immediately released, but may remain present within Redis.
306 | A special error is returned for this indicating this may be the case.
307 |
308 | If Redis crashes while a transaction is still held on a pod, then other pods will not be able to route transaction queries to this pod.
309 | The timeout will garbage collect these transactions, but the connection will remain held until it times out.
310 |
311 | ## Running distributed tests
312 |
313 | Run 2 instances connected to a local CRDB/Postgres and Redis like the following (DSN and Redis env vars omitted):
314 |
315 | ```POD_URL="localhost:8080" HTTP_PORT="8080" task```
316 |
317 | ```POD_URL="localhost:8081" HTTP_PORT="8081" task```
318 |
--------------------------------------------------------------------------------
/Taskfile.yaml:
--------------------------------------------------------------------------------
1 | # https://taskfile.dev
2 |
3 | version: '3'
4 |
5 | dotenv: ['.env']
6 |
7 | tasks:
8 | default:
9 | cmds:
10 | - go run .
11 | silent: true
12 | single-test:
13 | cmds:
14 | - go test --count=1 -v ./... -run {{.CLI_ARGS}}
15 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | crdb:
4 | image: cockroachdb/cockroach
5 | ports:
6 | - 26257:26257
7 | - 8083:8083
8 | command: start-single-node --insecure
9 | sqlgateway:
10 | image: ghcr.io/danthegoodman1/sqlgateway:latest
11 | ports:
12 | - 8080:8080
13 | environment:
14 | - PG_DSN=postgresql://root@crdb:26257/defaultdb
15 | depends_on:
16 | - crdb
17 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/danthegoodman1/SQLGateway
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/UltimateTournament/backoff/v4 v4.2.1
7 | github.com/cockroachdb/cockroach-go/v2 v2.2.16
8 | github.com/cockroachdb/cockroachdb-parser v0.0.0-20221108120757-a1ab1810b088
9 | github.com/go-playground/validator/v10 v10.11.1
10 | github.com/google/uuid v1.3.0
11 | github.com/jackc/pgconn v1.13.0
12 | github.com/jackc/pgtype v1.12.0
13 | github.com/jackc/pgx/v4 v4.17.2
14 | github.com/labstack/echo/v4 v4.9.1
15 | github.com/matoous/go-nanoid/v2 v2.0.0
16 | github.com/rs/zerolog v1.28.0
17 | github.com/segmentio/ksuid v1.0.4
18 | golang.org/x/net v0.1.0
19 | k8s.io/api v0.25.4
20 | k8s.io/apimachinery v0.25.4
21 | k8s.io/client-go v0.25.4
22 | )
23 |
24 | require (
25 | github.com/PuerkitoBio/purell v1.1.1 // indirect
26 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
27 | github.com/biogo/store v0.0.0-20201120204734-aad293a2328f // indirect
28 | github.com/cespare/xxhash/v2 v2.1.2 // indirect
29 | github.com/cockroachdb/apd/v3 v3.1.0 // indirect
30 | github.com/cockroachdb/errors v1.9.0 // indirect
31 | github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect
32 | github.com/cockroachdb/redact v1.1.3 // indirect
33 | github.com/davecgh/go-spew v1.1.1 // indirect
34 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
35 | github.com/dustin/go-humanize v1.0.0 // indirect
36 | github.com/emicklei/go-restful/v3 v3.8.0 // indirect
37 | github.com/getsentry/sentry-go v0.12.0 // indirect
38 | github.com/go-logr/logr v1.2.3 // indirect
39 | github.com/go-openapi/jsonpointer v0.19.5 // indirect
40 | github.com/go-openapi/jsonreference v0.19.5 // indirect
41 | github.com/go-openapi/swag v0.19.14 // indirect
42 | github.com/go-playground/locales v0.14.0 // indirect
43 | github.com/go-playground/universal-translator v0.18.0 // indirect
44 | github.com/go-redis/redis/v9 v9.0.0-rc.1 // indirect
45 | github.com/gogo/protobuf v1.3.2 // indirect
46 | github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
47 | github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
48 | github.com/golang/protobuf v1.5.2 // indirect
49 | github.com/google/gnostic v0.5.7-v3refs // indirect
50 | github.com/google/go-cmp v0.5.8 // indirect
51 | github.com/google/gofuzz v1.1.0 // indirect
52 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
53 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect
54 | github.com/jackc/pgio v1.0.0 // indirect
55 | github.com/jackc/pgpassfile v1.0.0 // indirect
56 | github.com/jackc/pgproto3/v2 v2.3.1 // indirect
57 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
58 | github.com/jackc/puddle v1.3.0 // indirect
59 | github.com/josharian/intern v1.0.0 // indirect
60 | github.com/json-iterator/go v1.1.12 // indirect
61 | github.com/kr/pretty v0.3.0 // indirect
62 | github.com/kr/text v0.2.0 // indirect
63 | github.com/labstack/gommon v0.4.0 // indirect
64 | github.com/leodido/go-urn v1.2.1 // indirect
65 | github.com/lib/pq v1.10.6 // indirect
66 | github.com/mailru/easyjson v0.7.6 // indirect
67 | github.com/mattn/go-colorable v0.1.12 // indirect
68 | github.com/mattn/go-isatty v0.0.14 // indirect
69 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
70 | github.com/modern-go/reflect2 v1.0.2 // indirect
71 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
72 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
73 | github.com/pierrre/geohash v1.0.0 // indirect
74 | github.com/pkg/errors v0.9.1 // indirect
75 | github.com/rogpeppe/go-internal v1.8.1 // indirect
76 | github.com/sasha-s/go-deadlock v0.3.1 // indirect
77 | github.com/spf13/pflag v1.0.5 // indirect
78 | github.com/twpayne/go-geom v1.4.1 // indirect
79 | github.com/twpayne/go-kml v1.5.2 // indirect
80 | github.com/valyala/bytebufferpool v1.0.0 // indirect
81 | github.com/valyala/fasttemplate v1.2.1 // indirect
82 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
83 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
84 | golang.org/x/sys v0.1.0 // indirect
85 | golang.org/x/term v0.1.0 // indirect
86 | golang.org/x/text v0.4.0 // indirect
87 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
88 | golang.org/x/tools v0.1.12 // indirect
89 | google.golang.org/appengine v1.6.7 // indirect
90 | google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad // indirect
91 | google.golang.org/grpc v1.50.1 // indirect
92 | google.golang.org/protobuf v1.28.0 // indirect
93 | gopkg.in/inf.v0 v0.9.1 // indirect
94 | gopkg.in/yaml.v2 v2.4.0 // indirect
95 | gopkg.in/yaml.v3 v3.0.1 // indirect
96 | k8s.io/klog/v2 v2.70.1 // indirect
97 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
98 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect
99 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
100 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
101 | sigs.k8s.io/yaml v1.2.0 // indirect
102 | )
103 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
34 | github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
35 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
36 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
37 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
38 | github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
39 | github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
40 | github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
41 | github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
42 | github.com/Codefor/geohash v0.0.0-20140723084247-1b41c28e3a9d h1:iG9B49Q218F/XxXNRM7k/vWf7MKmLIS8AcJV9cGN4nA=
43 | github.com/Codefor/geohash v0.0.0-20140723084247-1b41c28e3a9d/go.mod h1:RVnhzAX71far8Kc3TQeA0k/dcaEKUnTDSOyet/JCmGI=
44 | github.com/DATA-DOG/go-sqlmock v1.3.2 h1:2L2f5t3kKnCLxnClDD/PrDfExFFa1wjESgxHG/B1ibo=
45 | github.com/DATA-DOG/go-sqlmock v1.3.2/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
46 | github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
47 | github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
48 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
49 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
50 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
51 | github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
52 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
53 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
54 | github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
55 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
56 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
57 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
58 | github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
59 | github.com/TomiHiltunen/geohash-golang v0.0.0-20150112065804-b3e4e625abfb h1:wumPkzt4zaxO4rHPBrjDK8iZMR41C1qs7njNqlacwQg=
60 | github.com/TomiHiltunen/geohash-golang v0.0.0-20150112065804-b3e4e625abfb/go.mod h1:QiYsIBRQEO+Z4Rz7GoI+dsHVneZNONvhczuA+llOZNM=
61 | github.com/UltimateTournament/backoff/v4 v4.2.1 h1:3qmPcFjNOwjlmIGivXnDXt/w5DLidixtnXcwkvA9+ps=
62 | github.com/UltimateTournament/backoff/v4 v4.2.1/go.mod h1:Ch9kw9v89oy8lo6jaxSaoBg9jV3kC8oZFg68Upmslig=
63 | github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
64 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
65 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
66 | github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
67 | github.com/biogo/store v0.0.0-20201120204734-aad293a2328f h1:+6okTAeUsUrdQr/qN7fIODzowrjjCrnJDg/gkYqcSXY=
68 | github.com/biogo/store v0.0.0-20201120204734-aad293a2328f/go.mod h1:z52shMwD6SGwRg2iYFjjDwX5Ene4ENTw6HfXraUy/08=
69 | github.com/broady/gogeohash v0.0.0-20120525094510-7b2c40d64042 h1:iEdmkrNMLXbM7ecffOAtZJQOQUTE4iMonxrb5opUgE4=
70 | github.com/broady/gogeohash v0.0.0-20120525094510-7b2c40d64042/go.mod h1:f1L9YvXvlt9JTa+A17trQjSMM6bV40f+tHjB+Pi+Fqk=
71 | github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
72 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
73 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
74 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
75 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
76 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
77 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
78 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
79 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
80 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
81 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
82 | github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
83 | github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
84 | github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
85 | github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
86 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
87 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
88 | github.com/cockroachdb/apd/v3 v3.1.0 h1:MK3Ow7LH0W8zkd5GMKA1PvS9qG3bWFI95WaVNfyZJ/w=
89 | github.com/cockroachdb/apd/v3 v3.1.0/go.mod h1:6qgPBMXjATAdD/VefbRP9NoSLKjbB4LCoA7gN4LpHs4=
90 | github.com/cockroachdb/cockroach-go/v2 v2.2.16 h1:t9dmZuC9J2W8IDQDSIGXmP+fBuEJSsrGXxWQz4cYqBY=
91 | github.com/cockroachdb/cockroach-go/v2 v2.2.16/go.mod h1:xZ2VHjUEb/cySv0scXBx7YsBnHtLHkR1+w/w73b5i3M=
92 | github.com/cockroachdb/cockroachdb-parser v0.0.0-20221108120757-a1ab1810b088 h1:ivrGyTlMxY47dQuStmde56CD9hmaQmaIHP1jxpjJsck=
93 | github.com/cockroachdb/cockroachdb-parser v0.0.0-20221108120757-a1ab1810b088/go.mod h1:xfSU5qFpqIxEicpm/yRZc6Xz50/OrBEnrkPhU2vhNi8=
94 | github.com/cockroachdb/datadriven v1.0.1-0.20211007161720-b558070c3be0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4=
95 | github.com/cockroachdb/datadriven v1.0.1-0.20220214170620-9913f5bc19b7/go.mod h1:hi0MtSY3AYDQNDi83kDkMH5/yqM/CsIrsOITkSoH7KI=
96 | github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM=
97 | github.com/cockroachdb/errors v1.8.8/go.mod h1:z6VnEL3hZ/2ONZEvG7S5Ym0bU2AqPcEKnIiA1wbsSu0=
98 | github.com/cockroachdb/errors v1.9.0 h1:B48dYem5SlAY7iU8AKsgedb4gH6mo+bDkbtLIvM/a88=
99 | github.com/cockroachdb/errors v1.9.0/go.mod h1:vaNcEYYqbIqB5JhKBhFV9CneUqeuEbB2OYJBK4GBNYQ=
100 | github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
101 | github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f h1:6jduT9Hfc0njg5jJ1DdKCFPdMBrp/mdZfCpa5h+WM74=
102 | github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
103 | github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ=
104 | github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
105 | github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ=
106 | github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
107 | github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
108 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
109 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
110 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
111 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
112 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
113 | github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
114 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
115 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
116 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
117 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
118 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
119 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
120 | github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
121 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
122 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
123 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
124 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
125 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
126 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
127 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
128 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
129 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
130 | github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
131 | github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw=
132 | github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
133 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
134 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
135 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
136 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
137 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
138 | github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
139 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
140 | github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
141 | github.com/fanixk/geohash v0.0.0-20150324002647-c1f9b5fa157a h1:Fyfh/dsHFrC6nkX7H7+nFdTd1wROlX/FxEIWVpKYf1U=
142 | github.com/fanixk/geohash v0.0.0-20150324002647-c1f9b5fa157a/go.mod h1:UgNw+PTmmGN8rV7RvjvnBMsoTU8ZXXnaT3hYsDTBlgQ=
143 | github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
144 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
145 | github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
146 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
147 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
148 | github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
149 | github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
150 | github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
151 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
152 | github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
153 | github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
154 | github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
155 | github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
156 | github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
157 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
158 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
159 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
160 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
161 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
162 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
163 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
164 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
165 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
166 | github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
167 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
168 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
169 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
170 | github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
171 | github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
172 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
173 | github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
174 | github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
175 | github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
176 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
177 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
178 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
179 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
180 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
181 | github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
182 | github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
183 | github.com/go-redis/redis/v9 v9.0.0-rc.1 h1:/+bS+yeUnanqAbuD3QwlejzQZ+4eqgfUtFTG4b+QnXs=
184 | github.com/go-redis/redis/v9 v9.0.0-rc.1/go.mod h1:8et+z03j0l8N+DvsVnclzjf3Dl/pFHgRk+2Ct1qw66A=
185 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
186 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
187 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
188 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
189 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
190 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
191 | github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
192 | github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
193 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
194 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
195 | github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
196 | github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
197 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
198 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
199 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
200 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
201 | github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
202 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
203 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
204 | github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
205 | github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
206 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
207 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
208 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
209 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
210 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
211 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
212 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
213 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
214 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
215 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
216 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
217 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
218 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
219 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
220 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
221 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
222 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
223 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
224 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
225 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
226 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
227 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
228 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
229 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
230 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
231 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
232 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
233 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
234 | github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
235 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
236 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
237 | github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
238 | github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
239 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
240 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
241 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
242 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
243 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
244 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
245 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
246 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
247 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
248 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
249 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
250 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
251 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
252 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
253 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
254 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
255 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
256 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
257 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
258 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
259 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
260 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
261 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
262 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
263 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
264 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
265 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
266 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
267 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
268 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
269 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
270 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
271 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
272 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
273 | github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
274 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
275 | github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
276 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
277 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
278 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
279 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
280 | github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
281 | github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
282 | github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE=
283 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
284 | github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
285 | github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
286 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
287 | github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
288 | github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
289 | github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
290 | github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
291 | github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
292 | github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
293 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
294 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
295 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
296 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
297 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
298 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
299 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
300 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
301 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
302 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
303 | github.com/jackc/pgconn v1.12.0/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
304 | github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
305 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
306 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
307 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
308 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
309 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
310 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
311 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
312 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
313 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
314 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
315 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
316 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
317 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
318 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
319 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
320 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
321 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
322 | github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
323 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
324 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
325 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
326 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
327 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
328 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
329 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
330 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
331 | github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
332 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
333 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
334 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
335 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
336 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
337 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
338 | github.com/jackc/pgx/v4 v4.16.0/go.mod h1:N0A9sFdWzkw/Jy1lwoiB64F2+ugFZi987zRxcPez/wI=
339 | github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
340 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
341 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
342 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
343 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
344 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
345 | github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
346 | github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
347 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
348 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
349 | github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
350 | github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
351 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
352 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
353 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
354 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
355 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
356 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
357 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
358 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
359 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
360 | github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
361 | github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
362 | github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
363 | github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
364 | github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
365 | github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
366 | github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
367 | github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
368 | github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
369 | github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
370 | github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
371 | github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro=
372 | github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8=
373 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
374 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
375 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
376 | github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
377 | github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
378 | github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
379 | github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
380 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
381 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
382 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
383 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
384 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
385 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
386 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
387 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
388 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
389 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
390 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
391 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
392 | github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
393 | github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
394 | github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
395 | github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
396 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
397 | github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
398 | github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
399 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
400 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
401 | github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
402 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
403 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
404 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
405 | github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
406 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
407 | github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
408 | github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
409 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
410 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
411 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
412 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
413 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
414 | github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
415 | github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
416 | github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
417 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
418 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
419 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
420 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
421 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
422 | github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
423 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
424 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
425 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
426 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
427 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
428 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
429 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
430 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
431 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
432 | github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
433 | github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
434 | github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
435 | github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
436 | github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
437 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
438 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
439 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
440 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
441 | github.com/mmcloughlin/geohash v0.9.0 h1:FihR004p/aE1Sju6gcVq5OLDqGcMnpBY+8moBqIsVOs=
442 | github.com/mmcloughlin/geohash v0.9.0/go.mod h1:oNZxQo5yWJh0eMQEP/8hwQuVx9Z9tjwFUqcTB1SmG0c=
443 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
444 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
445 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
446 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
447 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
448 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
449 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
450 | github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
451 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
452 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
453 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
454 | github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
455 | github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
456 | github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
457 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
458 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
459 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
460 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
461 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
462 | github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
463 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
464 | github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y=
465 | github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
466 | github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU=
467 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
468 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
469 | github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
470 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
471 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
472 | github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
473 | github.com/ory/dockertest/v3 v3.6.0/go.mod h1:4ZOpj8qBUmh8fcBSVzkH2bws2s91JdGvHUqan4GHEuQ=
474 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
475 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
476 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
477 | github.com/pierrre/compare v1.0.2 h1:k4IUsHgh+dbcAOIWCfxVa/7G6STjADH2qmhomv+1quc=
478 | github.com/pierrre/compare v1.0.2/go.mod h1:8UvyRHH+9HS8Pczdd2z5x/wvv67krDwVxoOndaIIDVU=
479 | github.com/pierrre/geohash v1.0.0 h1:f/zfjdV4rVofTCz1FhP07T+EMQAvcMM2ioGZVt+zqjI=
480 | github.com/pierrre/geohash v1.0.0/go.mod h1:atytaeVa21hj5F6kMebHYPf8JbIrGxK2FSzN2ajKXms=
481 | github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
482 | github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
483 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
484 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
485 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
486 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
487 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
488 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
489 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
490 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
491 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
492 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
493 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
494 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
495 | github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
496 | github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
497 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
498 | github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
499 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
500 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
501 | github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
502 | github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
503 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
504 | github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
505 | github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
506 | github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
507 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
508 | github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
509 | github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
510 | github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
511 | github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
512 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
513 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
514 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
515 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
516 | github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
517 | github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
518 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
519 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
520 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
521 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
522 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
523 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
524 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
525 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
526 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
527 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
528 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
529 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
530 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
531 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
532 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
533 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
534 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
535 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
536 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
537 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
538 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
539 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
540 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
541 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
542 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
543 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
544 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
545 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
546 | github.com/the42/cartconvert v0.0.0-20131203171324-aae784c392b8 h1:I4DY8wLxJXCrMYzDM6lKCGc3IQwJX0PlTLsd3nQqI3c=
547 | github.com/the42/cartconvert v0.0.0-20131203171324-aae784c392b8/go.mod h1:fWO/msnJVhHqN1yX6OBoxSyfj7TEj1hHiL8bJSQsK30=
548 | github.com/twpayne/go-geom v1.4.1 h1:LeivFqaGBRfyg0XJJ9pkudcptwhSSrYN9KZUW6HcgdA=
549 | github.com/twpayne/go-geom v1.4.1/go.mod h1:k/zktXdL+qnA6OgKsdEGUTA17jbQ2ZPTUa3CCySuGpE=
550 | github.com/twpayne/go-kml v1.5.2 h1:rFMw2/EwgkVssGS2MT6YfWSPZz6BgcJkLxQ53jnE8rQ=
551 | github.com/twpayne/go-kml v1.5.2/go.mod h1:kz8jAiIz6FIdU2Zjce9qGlVtgFYES9vt7BTPBHf5jl4=
552 | github.com/twpayne/go-polyline v1.0.0/go.mod h1:ICh24bcLYBX8CknfvNPKqoTbe+eg+MX1NPyJmSBo7pU=
553 | github.com/twpayne/go-waypoint v0.0.0-20200706203930-b263a7f6e4e8/go.mod h1:qj5pHncxKhu9gxtZEYWypA/z097sxhFlbTyOyt9gcnU=
554 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
555 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
556 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
557 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
558 | github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
559 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
560 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
561 | github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
562 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
563 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
564 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
565 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
566 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
567 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
568 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
569 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
570 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
571 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
572 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
573 | github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
574 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
575 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
576 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
577 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
578 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
579 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
580 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
581 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
582 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
583 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
584 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
585 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
586 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
587 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
588 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
589 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
590 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
591 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
592 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
593 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
594 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
595 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
596 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
597 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
598 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
599 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
600 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
601 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
602 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
603 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
604 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
605 | golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
606 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
607 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
608 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
609 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
610 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
611 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
612 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
613 | golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
614 | golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
615 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
616 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
617 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
618 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
619 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
620 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
621 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
622 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
623 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
624 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
625 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
626 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
627 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
628 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
629 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
630 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
631 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
632 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
633 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
634 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
635 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
636 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
637 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
638 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
639 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
640 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
641 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
642 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
643 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
644 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
645 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
646 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
647 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
648 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
649 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
650 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
651 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
652 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
653 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
654 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
655 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
656 | golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
657 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
658 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
659 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
660 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
661 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
662 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
663 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
664 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
665 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
666 | golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
667 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
668 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
669 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
670 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
671 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
672 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
673 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
674 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
675 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
676 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
677 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
678 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
679 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
680 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
681 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
682 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
683 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
684 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
685 | golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
686 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
687 | golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
688 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
689 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
690 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
691 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
692 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
693 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
694 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
695 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
696 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
697 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
698 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
699 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
700 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
701 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
702 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
703 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
704 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
705 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
706 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
707 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
708 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
709 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
710 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
711 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
712 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
713 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
714 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
715 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
716 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
717 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
718 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
719 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
720 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
721 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
722 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
723 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
724 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
725 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
726 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
727 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
728 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
729 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
730 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
731 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
732 | golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
733 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
734 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
735 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
736 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
737 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
738 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
739 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
740 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
741 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
742 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
743 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
744 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
745 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
746 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
747 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
748 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
749 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
750 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
751 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
752 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
753 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
754 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
755 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
756 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
757 | golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
758 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
759 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
760 | golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
761 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
762 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
763 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
764 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
765 | golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
766 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
767 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
768 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
769 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
770 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
771 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
772 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
773 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
774 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
775 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
776 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
777 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
778 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
779 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
780 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
781 | golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
782 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
783 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
784 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
785 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
786 | golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
787 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
788 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
789 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
790 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
791 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
792 | golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
793 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
794 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
795 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
796 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
797 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
798 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
799 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
800 | golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
801 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
802 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
803 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
804 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
805 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
806 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
807 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
808 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
809 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
810 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
811 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
812 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
813 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
814 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
815 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
816 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
817 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
818 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
819 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
820 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
821 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
822 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
823 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
824 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
825 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
826 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
827 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
828 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
829 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
830 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
831 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
832 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
833 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
834 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
835 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
836 | golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
837 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
838 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
839 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
840 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
841 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
842 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
843 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
844 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
845 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
846 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
847 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
848 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
849 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
850 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
851 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
852 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
853 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
854 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
855 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
856 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
857 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
858 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
859 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
860 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
861 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
862 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
863 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
864 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
865 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
866 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
867 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
868 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
869 | google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
870 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
871 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
872 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
873 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
874 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
875 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
876 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
877 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
878 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
879 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
880 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
881 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
882 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
883 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
884 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
885 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
886 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
887 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
888 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
889 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
890 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
891 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
892 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
893 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
894 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
895 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
896 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
897 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
898 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
899 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
900 | google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
901 | google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
902 | google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad h1:kqrS+lhvaMHCxul6sKQvKJ8nAAhlVItmZV822hYFH/U=
903 | google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
904 | google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
905 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
906 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
907 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
908 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
909 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
910 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
911 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
912 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
913 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
914 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
915 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
916 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
917 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
918 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
919 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
920 | google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
921 | google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
922 | google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
923 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
924 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
925 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
926 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
927 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
928 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
929 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
930 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
931 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
932 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
933 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
934 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
935 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
936 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
937 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
938 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
939 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
940 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
941 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
942 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
943 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
944 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
945 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
946 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
947 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
948 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
949 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
950 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
951 | gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
952 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
953 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
954 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
955 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
956 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
957 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
958 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
959 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
960 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
961 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
962 | gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
963 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
964 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
965 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
966 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
967 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
968 | gorm.io/driver/postgres v1.3.5/go.mod h1:EGCWefLFQSVFrHGy4J8EtiHCWX5Q8t0yz2Jt9aKkGzU=
969 | gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
970 | gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
971 | gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
972 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
973 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
974 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
975 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
976 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
977 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
978 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
979 | k8s.io/api v0.25.4 h1:3YO8J4RtmG7elEgaWMb4HgmpS2CfY1QlaOz9nwB+ZSs=
980 | k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ=
981 | k8s.io/apimachinery v0.25.4 h1:CtXsuaitMESSu339tfhVXhQrPET+EiWnIY1rcurKnAc=
982 | k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
983 | k8s.io/client-go v0.25.4 h1:3RNRDffAkNU56M/a7gUfXaEzdhZlYhoW8dgViGy5fn8=
984 | k8s.io/client-go v0.25.4/go.mod h1:8trHCAC83XKY0wsBIpbirZU4NTUpbuhc2JnI7OruGZw=
985 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
986 | k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ=
987 | k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
988 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA=
989 | k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU=
990 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
991 | k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
992 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
993 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
994 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
995 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
996 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
997 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
998 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
999 | sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
1000 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
1001 |
--------------------------------------------------------------------------------
/gologger/gologger.go:
--------------------------------------------------------------------------------
1 | package gologger
2 |
3 | import (
4 | "os"
5 | "runtime"
6 | "strconv"
7 | "strings"
8 | "time"
9 |
10 | "github.com/rs/zerolog"
11 | )
12 |
13 | type ctxKey string
14 |
15 | const ReqIDKey ctxKey = "reqID"
16 |
17 | type (
18 | Action struct {
19 | DurationNS int64
20 | Statement string `json:",omitempty"`
21 | Error string `json:",omitempty"`
22 | Exec bool `json:",omitempty"`
23 | NumRows *int `json:",omitempty"`
24 | }
25 | )
26 |
27 | func init() {
28 | l := NewLogger()
29 | zerolog.DefaultContextLogger = &l
30 | zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
31 | function := ""
32 | fun := runtime.FuncForPC(pc)
33 | if fun != nil {
34 | funName := fun.Name()
35 | slash := strings.LastIndex(funName, "/")
36 | if slash > 0 {
37 | funName = funName[slash+1:]
38 | }
39 | function = " " + funName + "()"
40 | }
41 | return file + ":" + strconv.Itoa(line) + function
42 | }
43 | }
44 |
45 | func NewLogger() zerolog.Logger {
46 | zerolog.TimeFieldFormat = time.RFC3339Nano
47 | zerolog.TimestampFieldName = "time"
48 |
49 | logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
50 |
51 | logger = logger.Hook(CallerHook{})
52 |
53 | if os.Getenv("PRETTY") == "1" {
54 | logger = logger.Output(zerolog.ConsoleWriter{Out: os.Stderr})
55 | }
56 | if os.Getenv("TRACE") == "1" {
57 | zerolog.SetGlobalLevel(zerolog.TraceLevel)
58 | } else if os.Getenv("DEBUG") == "1" {
59 | zerolog.SetGlobalLevel(zerolog.DebugLevel)
60 | } else {
61 | zerolog.SetGlobalLevel(zerolog.InfoLevel)
62 | }
63 |
64 | return logger
65 | }
66 |
67 | type CallerHook struct{}
68 |
69 | func (h CallerHook) Run(e *zerolog.Event, _ zerolog.Level, _ string) {
70 | e.Caller(3)
71 | }
72 |
--------------------------------------------------------------------------------
/http_server/custom_context.go:
--------------------------------------------------------------------------------
1 | package http_server
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "net/http"
7 |
8 | "github.com/danthegoodman1/SQLGateway/gologger"
9 | "github.com/google/uuid"
10 | "github.com/labstack/echo/v4"
11 | "github.com/rs/zerolog"
12 | )
13 |
14 | type CustomContext struct {
15 | echo.Context
16 | RequestID string
17 | UserID string
18 | }
19 |
20 | func CreateReqContext(next echo.HandlerFunc) echo.HandlerFunc {
21 | return func(c echo.Context) error {
22 | reqID := uuid.NewString()
23 | ctx := context.WithValue(c.Request().Context(), gologger.ReqIDKey, reqID)
24 | ctx = logger.WithContext(ctx)
25 | c.SetRequest(c.Request().WithContext(ctx))
26 | logger := zerolog.Ctx(ctx)
27 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
28 | return c.Str("reqID", reqID)
29 | })
30 | cc := &CustomContext{
31 | Context: c,
32 | RequestID: reqID,
33 | }
34 | return next(cc)
35 | }
36 | }
37 |
38 | // Casts to custom context for the handler, so this doesn't have to be done per handler
39 | func ccHandler(h func(*CustomContext) error) echo.HandlerFunc {
40 | // TODO: Include the path?
41 | return func(c echo.Context) error {
42 | return h(c.(*CustomContext))
43 | }
44 | }
45 |
46 | func (c *CustomContext) internalErrorMessage() string {
47 | return "internal error, request id: " + c.RequestID
48 | }
49 |
50 | func (c *CustomContext) InternalError(err error, msg string) error {
51 | if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
52 | zerolog.Ctx(c.Request().Context()).Warn().CallerSkipFrame(1).Msg(err.Error())
53 | } else {
54 | zerolog.Ctx(c.Request().Context()).Error().CallerSkipFrame(1).Err(err).Msg(msg)
55 | }
56 | return c.String(http.StatusInternalServerError, c.internalErrorMessage())
57 | }
58 |
--------------------------------------------------------------------------------
/http_server/http_server.go:
--------------------------------------------------------------------------------
1 | package http_server
2 |
3 | import (
4 | "context"
5 | "crypto/subtle"
6 | "errors"
7 | "fmt"
8 | "net"
9 | "net/http"
10 | "os"
11 | "time"
12 |
13 | "github.com/danthegoodman1/SQLGateway/gologger"
14 | "github.com/danthegoodman1/SQLGateway/utils"
15 | "github.com/go-playground/validator/v10"
16 | "github.com/labstack/echo/v4"
17 | "github.com/labstack/echo/v4/middleware"
18 | "github.com/rs/zerolog"
19 | "golang.org/x/net/http2"
20 | )
21 |
22 | var logger = gologger.NewLogger()
23 |
24 | type HTTPServer struct {
25 | Echo *echo.Echo
26 | }
27 |
28 | type CustomValidator struct {
29 | validator *validator.Validate
30 | }
31 |
32 | func StartHTTPServer() *HTTPServer {
33 | listener, err := net.Listen("tcp", fmt.Sprintf(":%s", utils.HTTP_PORT))
34 | if err != nil {
35 | logger.Error().Err(err).Msg("error creating tcp listener, exiting")
36 | os.Exit(1)
37 | }
38 | s := &HTTPServer{
39 | Echo: echo.New(),
40 | }
41 | s.Echo.HideBanner = true
42 | s.Echo.HidePort = true
43 | s.Echo.JSONSerializer = &utils.NoEscapeJSONSerializer{}
44 |
45 | s.Echo.Use(CreateReqContext)
46 | s.Echo.Use(LoggerMiddleware)
47 | s.Echo.Use(middleware.CORS())
48 | s.Echo.Validator = &CustomValidator{validator: validator.New()}
49 |
50 | // technical - no auth
51 | s.Echo.GET("/hc", s.HealthCheck)
52 |
53 | if utils.AUTH_USER != "" && utils.AUTH_PASS != "" {
54 | logger.Debug().Msg("using basic auth")
55 | s.Echo.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
56 | if subtle.ConstantTimeCompare([]byte(username), []byte(utils.AUTH_USER)) == 1 && subtle.ConstantTimeCompare([]byte(password), []byte(utils.AUTH_PASS)) == 1 {
57 | return true, nil
58 | }
59 | return false, nil
60 | }))
61 | }
62 |
63 | psqlGroup := s.Echo.Group("/psql")
64 | psqlGroup.POST("/query", ccHandler(s.PostQuery))
65 | psqlGroup.POST("/begin", ccHandler(s.PostBegin))
66 | psqlGroup.POST("/commit", ccHandler(s.PostCommit))
67 | psqlGroup.POST("/rollback", ccHandler(s.PostRollback))
68 |
69 | s.Echo.Listener = listener
70 | go func() {
71 | logger.Info().Msg("starting h2c server on " + listener.Addr().String())
72 | err := s.Echo.StartH2CServer("", &http2.Server{})
73 | // stop the broker
74 | if err != nil && !errors.Is(err, http.ErrServerClosed) {
75 | logger.Error().Err(err).Msg("failed to start h2c server, exiting")
76 | os.Exit(1)
77 | }
78 | }()
79 |
80 | return s
81 | }
82 |
83 | func (cv *CustomValidator) Validate(i interface{}) error {
84 | if err := cv.validator.Struct(i); err != nil {
85 | return echo.NewHTTPError(http.StatusBadRequest, err.Error())
86 | }
87 | return nil
88 | }
89 |
90 | func ValidateRequest(c echo.Context, s interface{}) error {
91 | if err := c.Bind(s); err != nil {
92 | return echo.NewHTTPError(http.StatusBadRequest, err.Error())
93 | }
94 | if err := c.Validate(s); err != nil {
95 | return err
96 | }
97 | return nil
98 | }
99 |
100 | func (*HTTPServer) HealthCheck(c echo.Context) error {
101 | return c.String(http.StatusOK, "ok")
102 | }
103 |
104 | func (s *HTTPServer) Shutdown(ctx context.Context) error {
105 | err := s.Echo.Shutdown(ctx)
106 | return err
107 | }
108 |
109 | func LoggerMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
110 | return func(c echo.Context) error {
111 | start := time.Now()
112 | if err := next(c); err != nil {
113 | // default handler
114 | c.Error(err)
115 | }
116 | stop := time.Since(start)
117 | // Log otherwise
118 | logger := zerolog.Ctx(c.Request().Context())
119 | req := c.Request()
120 | res := c.Response()
121 |
122 | p := req.URL.Path
123 | if p == "" {
124 | p = "/"
125 | }
126 |
127 | cl := req.Header.Get(echo.HeaderContentLength)
128 | if cl == "" {
129 | cl = "0"
130 | }
131 | logger.Debug().Str("method", req.Method).Str("remote_ip", c.RealIP()).Str("req_uri", req.RequestURI).Str("handler_path", c.Path()).Str("path", p).Int("status", res.Status).Int64("latency_ns", int64(stop)).Str("protocol", req.Proto).Str("bytes_in", cl).Int64("bytes_out", res.Size).Msg("req recived")
132 | return nil
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/http_server/http_server_test.go:
--------------------------------------------------------------------------------
1 | package http_server
2 |
3 | //
4 | //import (
5 | // "testing"
6 | //)
7 | //
8 | ////func TestPSQLIsSelectOnly(t *testing.T) {
9 | //// selectOnly, err := PSQLIsSelectOnly("SELECT 1")
10 | //// if err != nil {
11 | //// t.Fatal(err)
12 | //// }
13 | //// if !selectOnly {
14 | //// t.Fatal("not select only")
15 | //// }
16 | ////
17 | //// selectOnly, err = PSQLIsSelectOnly("SELECT (SELECT 1, 2 as iuu) as heheh")
18 | //// if err != nil {
19 | //// t.Fatal(err)
20 | //// }
21 | //// if !selectOnly {
22 | //// t.Fatal("not select only")
23 | //// }
24 | ////
25 | //// selectOnly, err = PSQLIsSelectOnly(`UPDATE dummy
26 | ////SET customer=subquery.customer,
27 | //// address=subquery.address,
28 | //// partn=subquery.partn
29 | ////FROM (SELECT address_id, customer, address, partn
30 | //// FROM hehe) AS subquery
31 | ////WHERE dummy.address_id=subquery.address_id;`)
32 | //// if err != nil {
33 | //// t.Fatal(err)
34 | //// }
35 | //// if selectOnly {
36 | //// t.Fatal("select only")
37 | //// }
38 | ////
39 | //// selectOnly, err = PSQLIsSelectOnly(`insert into items_ver
40 | ////select * from items where item_id=2;`)
41 | //// if err != nil {
42 | //// t.Fatal(err)
43 | //// }
44 | //// if selectOnly {
45 | //// t.Fatal("select only")
46 | //// }
47 | ////
48 | //// selectOnly, err = PSQLIsSelectOnly(`UPDATE a SET b = 'c' WHERE b = 'c' RETURNING *`)
49 | //// if err != nil {
50 | //// t.Fatal(err)
51 | //// }
52 | //// if selectOnly {
53 | //// t.Fatal("select only")
54 | //// }
55 | ////}
56 | //
57 | //func TestCRDBIsSelectOnly(t *testing.T) {
58 | // selectOnly, err := CRDBIsSelectOnly("SELECT 1")
59 | // if err != nil {
60 | // t.Fatal(err)
61 | // }
62 | // if !selectOnly {
63 | // t.Fatal("not select only")
64 | // }
65 | //
66 | // selectOnly, err = CRDBIsSelectOnly("SELECT (SELECT 1, 2 as iuu) as heheh")
67 | // if err != nil {
68 | // t.Fatal(err)
69 | // }
70 | // if !selectOnly {
71 | // t.Fatal("not select only")
72 | // }
73 | //
74 | // selectOnly, err = CRDBIsSelectOnly(`UPDATE dummy
75 | //SET customer=subquery.customer,
76 | // address=subquery.address,
77 | // partn=subquery.partn
78 | //FROM (SELECT address_id, customer, address, partn
79 | // FROM hehe) AS subquery
80 | //WHERE dummy.address_id=subquery.address_id;`)
81 | // if err != nil {
82 | // t.Fatal(err)
83 | // }
84 | // if selectOnly {
85 | // t.Fatal("select only")
86 | // }
87 | //
88 | // selectOnly, err = CRDBIsSelectOnly(`insert into items_ver
89 | //select * from items where item_id=2;`)
90 | // if err != nil {
91 | // t.Fatal(err)
92 | // }
93 | // if selectOnly {
94 | // t.Fatal("select only")
95 | // }
96 | //
97 | // selectOnly, err = CRDBIsSelectOnly(`UPDATE a SET b = 'c' WHERE b = 'c' RETURNING *`)
98 | // if err != nil {
99 | // t.Fatal(err)
100 | // }
101 | // if selectOnly {
102 | // t.Fatal("select only")
103 | // }
104 | //
105 | // selectOnly, err = CRDBIsSelectOnly(`SELECT somfunc() FROM wefuhw AS OF SYSTEM TIME follower_read_timestamp()`)
106 | // if err != nil {
107 | // t.Fatal(err)
108 | // }
109 | // if !selectOnly {
110 | // t.Fatal("not select only")
111 | // }
112 | //}
113 |
--------------------------------------------------------------------------------
/http_server/query.go:
--------------------------------------------------------------------------------
1 | package http_server
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "github.com/danthegoodman1/SQLGateway/pg"
7 | "github.com/rs/zerolog"
8 | "net/http"
9 | "time"
10 | )
11 |
12 | func (s *HTTPServer) PostQuery(c *CustomContext) error {
13 | var body pg.QueryRequest
14 | if err := ValidateRequest(c, &body); err != nil {
15 | return c.String(http.StatusBadRequest, err.Error())
16 | }
17 | defer c.Request().Body.Close()
18 | //logger := zerolog.Ctx(c.Request().Context())
19 | //logger.Info().Msg(c.Request().URL.Path)
20 |
21 | logger := zerolog.Ctx(c.Request().Context())
22 | if body.TxID != nil {
23 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
24 | return c.Str("txID", *body.TxID)
25 | })
26 | }
27 |
28 | res, err := pg.Query(c.Request().Context(), pg.PGPool, body.Queries, body.TxID)
29 | if err != nil {
30 | if errors.Is(err.Err, pg.ErrTxNotFound) {
31 | return c.String(http.StatusNotFound, "transaction not found, did it timeout?")
32 | }
33 | if errors.Is(err.Err, pg.ErrTxNotFoundLocal) {
34 | return c.String(http.StatusNotFound, err.Err.Error())
35 | }
36 | if err.Err != nil {
37 | return c.InternalError(err.Err, "error handling query")
38 | }
39 | if err.Remote {
40 | return c.String(err.StatusCode, err.ErrString)
41 | }
42 | }
43 |
44 | return c.JSON(http.StatusOK, res)
45 | }
46 |
47 | func (s *HTTPServer) PostBegin(c *CustomContext) error {
48 | var body pg.BeginRequest
49 | if err := ValidateRequest(c, &body); err != nil {
50 | return c.String(http.StatusBadRequest, err.Error())
51 | }
52 | defer c.Request().Body.Close()
53 |
54 | ctx, cancel := context.WithTimeout(c.Request().Context(), time.Second*10)
55 | defer cancel()
56 |
57 | txID, err := pg.Manager.NewTx(ctx, body.TxTimeoutSec)
58 | if errors.Is(err, context.DeadlineExceeded) {
59 | return c.String(http.StatusRequestTimeout, "timed out waiting for free pool connection")
60 | }
61 | if err != nil {
62 | return c.InternalError(err, "error creating new transaction")
63 | }
64 |
65 | return c.JSON(http.StatusOK, pg.TxIDJSON{
66 | TxID: txID,
67 | })
68 | }
69 |
70 | func (s *HTTPServer) PostCommit(c *CustomContext) error {
71 | var body pg.TxIDJSON
72 | if err := ValidateRequest(c, &body); err != nil {
73 | return c.String(http.StatusBadRequest, err.Error())
74 | }
75 | defer c.Request().Body.Close()
76 |
77 | ctx, cancel := context.WithTimeout(c.Request().Context(), time.Second*10)
78 | defer cancel()
79 |
80 | err := pg.Manager.CommitTx(ctx, body.TxID)
81 | if err != nil {
82 | if errors.Is(err.Err, context.DeadlineExceeded) {
83 | return c.String(http.StatusRequestTimeout, "timed out waiting for free pool connection")
84 | }
85 | if errors.Is(err.Err, pg.ErrTxNotFound) {
86 | return c.String(http.StatusNotFound, "transaction not found")
87 | }
88 | if errors.Is(err.Err, pg.ErrTxNotFoundLocal) {
89 | return c.String(http.StatusNotFound, err.Err.Error())
90 | }
91 | if err.Err != nil {
92 | return c.InternalError(err.Err, "error committing transaction")
93 | }
94 | if err.Remote {
95 | return c.String(err.StatusCode, err.ErrString)
96 | }
97 | }
98 |
99 | return c.NoContent(http.StatusOK)
100 | }
101 |
102 | func (s *HTTPServer) PostRollback(c *CustomContext) error {
103 | var body pg.TxIDJSON
104 | if err := ValidateRequest(c, &body); err != nil {
105 | return c.String(http.StatusBadRequest, err.Error())
106 | }
107 | defer c.Request().Body.Close()
108 |
109 | ctx, cancel := context.WithTimeout(c.Request().Context(), time.Second*10)
110 | defer cancel()
111 |
112 | err := pg.Manager.RollbackTx(ctx, body.TxID)
113 | if err != nil {
114 | if errors.Is(err.Err, context.DeadlineExceeded) {
115 | return c.String(http.StatusRequestTimeout, "timed out waiting for free pool connection")
116 | }
117 | if errors.Is(err.Err, pg.ErrTxNotFound) {
118 | return c.String(http.StatusNotFound, "transaction not found")
119 | }
120 | if errors.Is(err.Err, pg.ErrTxNotFoundLocal) {
121 | return c.String(http.StatusNotFound, err.Err.Error())
122 | }
123 | if err.Err != nil {
124 | return c.InternalError(err.Err, "error rolling back transaction")
125 | }
126 | if err.Remote {
127 | return c.String(err.StatusCode, err.ErrString)
128 | }
129 | }
130 |
131 | return c.NoContent(http.StatusOK)
132 | }
133 |
--------------------------------------------------------------------------------
/k8s/test/deployment.yml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: SQLGateway
5 | namespace: default
6 | labels:
7 | app: SQLGateway
8 | spec:
9 | selector:
10 | matchLabels:
11 | app: SQLGateway
12 | replicas: 2
13 | strategy:
14 | rollingUpdate:
15 | maxSurge: 25%
16 | maxUnavailable: 25%
17 | type: RollingUpdate
18 | template:
19 | metadata:
20 | labels:
21 | app: SQLGateway
22 | spec:
23 | containers:
24 | - name: SQLGateway
25 | image: "sqlgateway:latest"
26 | imagePullPolicy: Never
27 | resources:
28 | limits:
29 | cpu: 200m
30 | memory: 500Mi
31 | requests:
32 | cpu: 100m
33 | memory: 200Mi
34 | env:
35 | - name: DEBUG
36 | value: "1"
37 | - name: POD_BASE_DOMAIN
38 | value: ".default.svc.cluster.local:8080"
39 | - name: POD_NAME
40 | valueFrom:
41 | fieldRef:
42 | fieldPath: metadata.name
43 | - name: PG_DSN
44 | value: "postgresql://root@crdb.default.svc.cluster.local:26257/defaultdb"
45 | ports:
46 | - containerPort: 8080
47 | name: http
48 | restartPolicy: Never
49 |
--------------------------------------------------------------------------------
/ksd/ksd.go:
--------------------------------------------------------------------------------
1 | package ksd
2 |
3 | import (
4 | "fmt"
5 | "github.com/danthegoodman1/SQLGateway/gologger"
6 | v1 "k8s.io/api/core/v1"
7 | "time"
8 |
9 | v12 "k8s.io/apimachinery/pkg/apis/meta/v1"
10 | "k8s.io/client-go/informers"
11 | "k8s.io/client-go/kubernetes"
12 | "k8s.io/client-go/rest"
13 | "k8s.io/client-go/tools/cache"
14 | )
15 |
16 | var (
17 | logger = gologger.NewLogger()
18 | )
19 |
20 | type (
21 | KSD struct {
22 | kubeClient *kubernetes.Clientset
23 | informerFactory informers.SharedInformerFactory
24 | podInformer cache.SharedIndexInformer
25 | stopChan chan struct{}
26 | }
27 | )
28 |
29 | // NewPodKSD creates a new endpoint watcher that listens for added, deleted, and updated endpoints.
30 | func NewPodKSD(namespace, labelSelector string, addFunc, deleteFunc func(obj *v1.Pod), updateFunc func(oldObj, newObj *v1.Pod)) (*KSD, error) {
31 | config, err := rest.InClusterConfig()
32 | if err != nil {
33 | return nil, fmt.Errorf("error in rest.InClusterConfig: %w", err)
34 | }
35 |
36 | client, err := kubernetes.NewForConfig(config)
37 | if err != nil {
38 | return nil, fmt.Errorf("error in kubernetes.NewForConfig: %w", err)
39 | }
40 | logger.Debug().Msg("starting informer factory")
41 |
42 | // TODO: Make time configurable
43 | resyncTime := 30 * time.Second
44 | informerFactory := informers.NewSharedInformerFactoryWithOptions(client, resyncTime, informers.WithNamespace(namespace), informers.WithTweakListOptions(func(options *v12.ListOptions) {
45 | options.LabelSelector = labelSelector
46 | }))
47 |
48 | podInformer := informerFactory.Core().V1().Pods().Informer()
49 | podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
50 | AddFunc: func(obj interface{}) {
51 | logger.Debug().Interface("obj", obj).Str("informer", "pod").Msg("got add event")
52 | addFunc(obj.(*v1.Pod))
53 | },
54 | UpdateFunc: func(oldObj, newObj interface{}) {
55 | logger.Debug().Interface("old_obj", oldObj).Interface("new_obj", newObj).Str("informer", "pod").Msg("got update event")
56 | updateFunc(oldObj.(*v1.Pod), newObj.(*v1.Pod))
57 | },
58 | DeleteFunc: func(obj interface{}) {
59 | logger.Debug().Interface("obj", obj).Str("informer", "pod").Msg("got delete event")
60 | deleteFunc(obj.(*v1.Pod))
61 | },
62 | })
63 | stopChan := make(chan struct{})
64 | go podInformer.Run(stopChan)
65 |
66 | ksd := &KSD{
67 | kubeClient: client,
68 | informerFactory: informerFactory,
69 | podInformer: podInformer,
70 | stopChan: stopChan,
71 | }
72 |
73 | return ksd, nil
74 | }
75 |
76 | func (k *KSD) Stop() {
77 | close(k.stopChan)
78 | }
79 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/danthegoodman1/SQLGateway/pg"
7 | "github.com/danthegoodman1/SQLGateway/red"
8 | "os"
9 | "os/signal"
10 | "syscall"
11 | "time"
12 |
13 | "github.com/danthegoodman1/SQLGateway/gologger"
14 | "github.com/danthegoodman1/SQLGateway/http_server"
15 | "github.com/danthegoodman1/SQLGateway/utils"
16 | )
17 |
18 | var logger = gologger.NewLogger()
19 |
20 | func main() {
21 | logger.Info().Msg("starting SQLGateway")
22 |
23 | if err := pg.ConnectToDB(); err != nil {
24 | logger.Error().Err(err).Msg("error connecting to PG Pool")
25 | os.Exit(1)
26 | }
27 |
28 | if utils.REDIS_ADDR != "" {
29 | if err := red.ConnectRedis(); err != nil {
30 | logger.Error().Err(err).Msg("error connecting to Redis")
31 | os.Exit(1)
32 | }
33 | }
34 |
35 | //if utils.K8S_SD {
36 | // k, err := ksd.NewPodKSD("default", "app=SQLGateway", func(obj *v1.Pod) {
37 | // //fmt.Println("ADD EVENT: %+v", obj)
38 | // }, func(obj *v1.Pod) {
39 | // //fmt.Println("DELETE EVENT: %+v", obj)
40 | // }, func(oldObj, newObj *v1.Pod) {
41 | // //fmt.Println("UPDATE EVENT OLD: %+v", oldObj)
42 | // //fmt.Println("UPDATE EVENT NEW: %+v", newObj)
43 | // })
44 | // if err != nil {
45 | // logger.Error().Err(err).Msg("failed to create new pod ksd")
46 | // os.Exit(1)
47 | // }
48 | // defer k.Stop()
49 | //}
50 |
51 | pg.Manager = pg.NewTxManager()
52 |
53 | httpServer := http_server.StartHTTPServer()
54 |
55 | c := make(chan os.Signal, 1)
56 | signal.Notify(c, os.Interrupt, syscall.SIGTERM)
57 | <-c
58 | logger.Warn().Msg("received shutdown signal!")
59 |
60 | // Convert the time to seconds
61 | sleepTime := utils.GetEnvOrDefaultInt("SHUTDOWN_SLEEP_SEC", 0)
62 | logger.Info().Msg(fmt.Sprintf("sleeping for %ds before exiting", sleepTime))
63 |
64 | time.Sleep(time.Second * time.Duration(sleepTime))
65 | logger.Info().Msg(fmt.Sprintf("slept for %ds, exiting", sleepTime))
66 |
67 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
68 | defer cancel()
69 | if err := httpServer.Shutdown(ctx); err != nil {
70 | logger.Error().Err(err).Msg("failed to shutdown HTTP server")
71 | } else {
72 | logger.Info().Msg("successfully shutdown HTTP server")
73 | }
74 | if utils.REDIS_ADDR != "" {
75 | if err := red.Shutdown(ctx); err != nil {
76 | logger.Error().Err(err).Msg("error shutting down redis connection")
77 | } else {
78 | logger.Info().Msg("shut down redis")
79 | }
80 | }
81 | pg.Manager.Shutdown()
82 | logger.Info().Msg("shut down tx manager")
83 | os.Exit(0)
84 | }
85 |
--------------------------------------------------------------------------------
/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "github.com/danthegoodman1/SQLGateway/pg"
6 | "github.com/danthegoodman1/SQLGateway/red"
7 | "github.com/danthegoodman1/SQLGateway/utils"
8 | "os"
9 | "testing"
10 | )
11 |
12 | func TestMain(t *testing.M) {
13 | if err := pg.ConnectToDB(); err != nil {
14 | logger.Error().Err(err).Msg("error connecting to PG Pool")
15 | os.Exit(1)
16 | }
17 |
18 | if utils.REDIS_ADDR != "" {
19 | if err := red.ConnectRedis(); err != nil {
20 | logger.Error().Err(err).Msg("error connecting to Redis")
21 | os.Exit(1)
22 | }
23 | }
24 |
25 | c := t.Run()
26 |
27 | pg.PGPool.Close()
28 | err := red.Shutdown(context.Background())
29 | if err != nil {
30 | logger.Error().Err(err).Msg("error shutting down to Redis")
31 | os.Exit(1)
32 | }
33 | logger.Debug().Msg("done tests")
34 | os.Exit(c)
35 | }
36 |
--------------------------------------------------------------------------------
/pg/pg.go:
--------------------------------------------------------------------------------
1 | package pg
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/danthegoodman1/SQLGateway/gologger"
8 | "github.com/danthegoodman1/SQLGateway/utils"
9 | "github.com/jackc/pgx/v4/pgxpool"
10 | )
11 |
12 | var (
13 | PGPool *pgxpool.Pool
14 |
15 | logger = gologger.NewLogger()
16 | )
17 |
18 | func ConnectToDB() error {
19 | logger.Debug().Msg("connecting to PG...")
20 | var err error
21 | config, err := pgxpool.ParseConfig(utils.PG_DSN)
22 | if err != nil {
23 | return err
24 | }
25 |
26 | config.MaxConns = int32(utils.PG_POOL_CONNS)
27 | config.MinConns = 1
28 | config.HealthCheckPeriod = time.Second * 5
29 | config.MaxConnLifetime = time.Minute * 30
30 | config.MaxConnIdleTime = time.Minute * 30
31 |
32 | PGPool, err = pgxpool.ConnectConfig(context.Background(), config)
33 | if err != nil {
34 | return err
35 | }
36 | logger.Debug().Msg("connected to PG")
37 | return nil
38 | }
39 |
--------------------------------------------------------------------------------
/pg/pool.go:
--------------------------------------------------------------------------------
1 | package pg
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "github.com/danthegoodman1/SQLGateway/gologger"
10 | "github.com/danthegoodman1/SQLGateway/red"
11 | "github.com/danthegoodman1/SQLGateway/utils"
12 | "github.com/go-redis/redis/v9"
13 | "github.com/jackc/pgconn"
14 | "github.com/jackc/pgx/v4"
15 | "github.com/jackc/pgx/v4/pgxpool"
16 | "github.com/rs/zerolog"
17 | "io"
18 | "net/http"
19 | "time"
20 | )
21 |
22 | type (
23 | QueryReq struct {
24 | Statement string
25 | Params []any
26 | IgnoreCache *bool
27 | ForceCache *bool
28 | Exec *bool
29 | TxKey *string
30 | }
31 |
32 | QueryRes struct {
33 | Columns []any `json:",omitempty"`
34 | Rows [][]any `json:",omitempty"`
35 | Error *string `json:",omitempty"`
36 | TimeNS *int64 `json:",omitempty"`
37 | CacheHit *bool `json:",omitempty"`
38 | Cached *bool `json:",omitempty"`
39 | }
40 |
41 | Queryable interface {
42 | Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error)
43 | Exec(ctx context.Context, sql string, args ...interface{}) (pgconn.CommandTag, error)
44 | }
45 |
46 | QueryRequest struct {
47 | Queries []*QueryReq
48 | TxID *string
49 | }
50 |
51 | QueryResponse struct {
52 | Queries []*QueryRes `json:",omitempty"`
53 |
54 | // Whether this was processed on a remote node
55 | Remote bool `json:",omitempty"`
56 | }
57 |
58 | TxIDJSON struct {
59 | TxID string
60 | }
61 |
62 | BeginRequest struct {
63 | TxTimeoutSec *int64
64 | }
65 |
66 | DistributedError struct {
67 | Err error
68 | Remote bool
69 | StatusCode int
70 | ErrString string
71 | }
72 | )
73 |
74 | var (
75 | ErrEndTx = utils.PermError("end tx")
76 | ErrTxNotFoundLocal = errors.New("transaction not found on local pod, maybe the node restarted with the same name, or the transaction aborted")
77 | )
78 |
79 | func Query(ctx context.Context, pool *pgxpool.Pool, queries []*QueryReq, txID *string) (*QueryResponse, *DistributedError) {
80 |
81 | qres := &QueryResponse{
82 | Queries: make([]*QueryRes, len(queries)),
83 | }
84 |
85 | logger := zerolog.Ctx(ctx)
86 | ctx, cancel := context.WithTimeout(ctx, time.Second*30)
87 | defer cancel()
88 |
89 | //if query.IgnoreCache == nil || *query.IgnoreCache == false {
90 | // // TODO: Check cache
91 | // res.CacheHit = utils.Ptr(true)
92 | // // Return if cache hit
93 | //}
94 | s := time.Now()
95 | defer TraceQueries(ctx, s, queries, qres)
96 |
97 | if txID != nil {
98 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
99 | return c.Str("txID", *txID)
100 | })
101 | logger.Debug().Msg("transaction detected, handling queries in transaction")
102 |
103 | tx := Manager.GetTx(*txID)
104 | if tx == nil && red.RedisClient != nil {
105 | // Check for remote transaction
106 | txMeta, err := red.GetTransaction(ctx, *txID)
107 | if errors.Is(err, redis.Nil) {
108 | return nil, &DistributedError{
109 | Err: ErrTxNotFound,
110 | }
111 | }
112 | if err != nil {
113 | return nil, &DistributedError{
114 | Err: fmt.Errorf("error in red.GetTransaction: %w", err),
115 | }
116 | }
117 |
118 | if txMeta.PodID == utils.POD_NAME {
119 | // The only case would be if this node restarted but maintained the same name, without removing transactions from redis
120 | return nil, &DistributedError{Err: ErrTxNotFoundLocal}
121 | }
122 |
123 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
124 | return c.Str("remoteURL", txMeta.PodURL)
125 | })
126 | logger.Debug().Msg("remote transaction found, forwarding")
127 |
128 | bodyJSON, err := json.Marshal(QueryRequest{
129 | Queries: queries,
130 | TxID: txID,
131 | })
132 | if err != nil {
133 | return nil, &DistributedError{Err: fmt.Errorf("error in json.Marhsal for remote request body: %w", err)}
134 | }
135 |
136 | ctx, cancel := context.WithTimeout(ctx, time.Second*30)
137 | defer cancel()
138 |
139 | req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s://%s/psql/query", utils.GetHTTPPrefix(), txMeta.PodURL), bytes.NewReader(bodyJSON))
140 | if err != nil {
141 | return nil, &DistributedError{Err: fmt.Errorf("error making http request for remote pod: %w", err)}
142 | }
143 | req.Header.Set("content-type", "application/json")
144 |
145 | res, err := http.DefaultClient.Do(req)
146 | if err != nil {
147 | return nil, &DistributedError{Err: fmt.Errorf("error doing request to remote pod: %w", err)}
148 | }
149 |
150 | resBodyBytes, err := io.ReadAll(res.Body)
151 | if err != nil {
152 | return nil, &DistributedError{Err: fmt.Errorf("error reading body bytes from remote pod response: %w", err)}
153 | }
154 |
155 | if res.StatusCode != 200 {
156 | return nil, &DistributedError{Remote: true, StatusCode: res.StatusCode, ErrString: string(resBodyBytes)}
157 | }
158 |
159 | err = json.Unmarshal(resBodyBytes, &qres)
160 | if err != nil {
161 | return nil, &DistributedError{Err: fmt.Errorf("error in json.Unmarhsal for remote response body: %w", err)}
162 | }
163 |
164 | if utils.TRACES {
165 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
166 | return c.Str("remote_pod", txMeta.PodURL)
167 | })
168 | }
169 |
170 | qres.Remote = true
171 |
172 | return qres, nil
173 | } else if tx == nil {
174 | logger.Debug().Msgf("transaction %s not found", *txID)
175 | return nil, &DistributedError{Err: ErrTxNotFound}
176 | }
177 |
178 | res, err := tx.RunQueries(ctx, queries)
179 | if err != nil {
180 | logger.Debug().Msg("error found when running queries in transaction, rolling back")
181 | err := Manager.RollbackTx(ctx, *txID)
182 | if err != nil {
183 | return qres, err
184 | }
185 | }
186 | qres.Queries = res
187 | return qres, nil
188 | }
189 |
190 | var queryErr error
191 | // If single item, don't do in tx
192 | if len(queries) == 1 {
193 | queryErr = utils.ReliableExec(ctx, pool, time.Second*60, func(ctx context.Context, conn *pgxpool.Conn) error {
194 | queryRes := runQuery(ctx, conn, utils.Deref(queries[0].Exec, false), queries[0].Statement, queries[0].Params)
195 | qres.Queries[0] = queryRes
196 | if queryRes.Error != nil {
197 | return ErrEndTx
198 | }
199 | return nil
200 | })
201 | } else {
202 | queryErr = utils.ReliableExecInTx(ctx, pool, 60*time.Second, func(ctx context.Context, conn pgx.Tx) (err error) {
203 | for i, query := range queries {
204 | queryRes := runQuery(ctx, conn, utils.Deref(query.Exec, false), query.Statement, query.Params)
205 | qres.Queries[i] = queryRes
206 | if queryRes.Error != nil {
207 | return ErrEndTx
208 | }
209 | }
210 | return nil
211 | })
212 | }
213 |
214 | if queryErr != nil && !errors.Is(queryErr, ErrEndTx) {
215 | return nil, &DistributedError{Err: fmt.Errorf("error in transaction execution: %w", queryErr)}
216 | }
217 |
218 | return qres, nil
219 | }
220 |
221 | func runQuery(ctx context.Context, q Queryable, exec bool, statement string, params []any) (res *QueryRes) {
222 | res = &QueryRes{
223 | Rows: make([][]any, 0),
224 | }
225 |
226 | logger := zerolog.Ctx(ctx)
227 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
228 | return c.Str("statement", statement)
229 | })
230 |
231 | s := time.Now()
232 | defer func() {
233 | res.TimeNS = utils.Ptr(time.Since(s).Nanoseconds())
234 | }()
235 |
236 | if exec {
237 | _, err := q.Exec(ctx, statement, params...)
238 | if err != nil {
239 | res.Error = utils.Ptr(err.Error())
240 | logger.Warn().Err(err).Msg("got exec error")
241 | }
242 | } else {
243 | // Get columns
244 | rows, err := q.Query(ctx, statement, params...)
245 | if err != nil {
246 | res.Error = utils.Ptr(err.Error())
247 | logger.Warn().Err(err).Msg("got query error")
248 | return
249 | }
250 | //colNames := make([]any, len(rows.FieldDescriptions()))
251 | for _, desc := range rows.FieldDescriptions() {
252 | res.Columns = append(res.Columns, string(desc.Name))
253 | }
254 |
255 | // Get res values
256 | for rows.Next() {
257 | rowVals, err := rows.Values()
258 | if err != nil {
259 | res.Error = utils.Ptr(err.Error())
260 | return
261 | }
262 | res.Rows = append(res.Rows, rowVals)
263 | }
264 | }
265 |
266 | //selectOnly, err := CRDBIsSelectOnly(query.Statement)
267 | //if err != nil {
268 | // return nil, fmt.Errorf("error checking if select only: %w", err)
269 | //} else if selectOnly && ShouldCache(query.IgnoreCache, query.ForceCache) {
270 | // // TODO: Cache result
271 | // res.Cached = utils.Ptr(true)
272 | //}
273 |
274 | return
275 | }
276 |
277 | //func ShouldCache(ignoreCache, forceCache *bool) bool {
278 | // if ignoreCache != nil {
279 | // return *ignoreCache
280 | // }
281 | // if forceCache != nil {
282 | // return *forceCache
283 | // }
284 | //
285 | // return utils.CACHE_DEFAULT
286 | //}
287 |
288 | //func CRDBIsSelectOnly(statement string) (selectOnly bool, err error) {
289 | // ast, err := parser.ParseOne(statement)
290 | // if err != nil {
291 | // return false, fmt.Errorf("error in parser.ParseOne: %w", err)
292 | // }
293 | //
294 | // return ast.AST.StatementTag() == "SELECT", nil
295 | //}
296 |
297 | func TraceQueries(ctx context.Context, start time.Time, queries []*QueryReq, qres *QueryResponse) {
298 | if utils.TRACES {
299 | zerolog.Ctx(ctx).UpdateContext(func(c zerolog.Context) zerolog.Context {
300 | c = c.Interface("query_handler", gologger.Action{
301 | DurationNS: time.Since(start).Nanoseconds(),
302 | })
303 |
304 | if !qres.Remote {
305 | // We don't include these for pods that delegate to another pod, since that pod will include them
306 | actionLogs := make([]gologger.Action, 0)
307 |
308 | for i, queryRes := range qres.Queries {
309 | if queryRes == nil {
310 | continue
311 | }
312 | execd := utils.Deref(queries[i].Exec, false)
313 | actionLog := gologger.Action{
314 | DurationNS: *queryRes.TimeNS,
315 | Statement: queries[i].Statement,
316 | Exec: execd,
317 | }
318 | if !execd {
319 | actionLog.NumRows = utils.Ptr(len(queryRes.Rows))
320 | }
321 | if queryRes.Error != nil {
322 | actionLog.Error = *queryRes.Error
323 | }
324 |
325 | actionLogs = append(actionLogs, actionLog)
326 | }
327 |
328 | c = c.Interface("queries", actionLogs)
329 | }
330 |
331 | return c
332 | })
333 | }
334 | }
335 |
--------------------------------------------------------------------------------
/pg/tx.go:
--------------------------------------------------------------------------------
1 | package pg
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "github.com/danthegoodman1/SQLGateway/utils"
7 | "github.com/jackc/pgx/v4"
8 | "github.com/jackc/pgx/v4/pgxpool"
9 | "sync"
10 | "time"
11 | )
12 |
13 | type (
14 | Tx struct {
15 | ID string
16 | PoolConn *pgxpool.Conn
17 | Tx pgx.Tx
18 | Expires time.Time
19 | CancelChan chan bool
20 | Exited bool
21 | PoolMu *sync.Mutex
22 | }
23 | )
24 |
25 | var (
26 | ErrTxError = errors.New("transaction error")
27 | )
28 |
29 | func (tx *Tx) RunQueries(ctx context.Context, queries []*QueryReq) ([]*QueryRes, error) {
30 | tx.PoolMu.Lock()
31 | defer tx.PoolMu.Unlock()
32 |
33 | res := make([]*QueryRes, len(queries))
34 | for i, query := range queries {
35 | queryRes := runQuery(ctx, tx.PoolConn, utils.Deref(query.Exec, false), query.Statement, query.Params)
36 | res[i] = queryRes
37 | if queryRes.Error != nil {
38 | return res, ErrTxError
39 | }
40 | }
41 | return res, nil
42 | }
43 |
--------------------------------------------------------------------------------
/pg/tx_manager.go:
--------------------------------------------------------------------------------
1 | package pg
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "github.com/danthegoodman1/SQLGateway/red"
10 | "github.com/danthegoodman1/SQLGateway/utils"
11 | "github.com/go-redis/redis/v9"
12 | "github.com/jackc/pgx/v4"
13 | "github.com/rs/zerolog"
14 | "io"
15 | "net/http"
16 | "sync"
17 | "time"
18 | )
19 |
20 | type (
21 | TxManager struct {
22 | txMu *sync.Mutex
23 | txMap map[string]*Tx
24 | tickerStopChan chan bool
25 | ticker *time.Ticker
26 | }
27 | )
28 |
29 | var (
30 | ErrTxNotFound = errors.New("transaction not found")
31 |
32 | Manager *TxManager
33 | )
34 |
35 | func NewTxManager() *TxManager {
36 | txManager := &TxManager{
37 | txMu: &sync.Mutex{},
38 | txMap: map[string]*Tx{},
39 | ticker: time.NewTicker(time.Second * 2),
40 | tickerStopChan: make(chan bool, 1),
41 | }
42 |
43 | go func(manager *TxManager) {
44 | logger.Debug().Msg("starting redis background worker")
45 | for {
46 | select {
47 | case <-manager.ticker.C:
48 | go manager.handleExpiredTransactions()
49 | case <-manager.tickerStopChan:
50 | return
51 | }
52 | }
53 | }(txManager)
54 |
55 | return txManager
56 | }
57 |
58 | // NewTx starts a new transaction, returning the ID
59 | func (manager *TxManager) NewTx(ctx context.Context, timeoutSec *int64) (string, error) {
60 | txID := utils.GenRandomID("tx")
61 |
62 | expireTime := time.Now().Add(time.Second * time.Duration(utils.Deref(timeoutSec, 30)))
63 | poolConn, err := PGPool.Acquire(ctx)
64 | if err != nil {
65 | return "", fmt.Errorf("error in PGPool.Acquire: %w", err)
66 | }
67 |
68 | txCtx, cancel := context.WithTimeout(context.Background(), time.Second*30)
69 | pgTx, err := poolConn.BeginTx(ctx, pgx.TxOptions{})
70 | if err != nil {
71 | cancel()
72 | return "", fmt.Errorf("error in : %w", err)
73 | }
74 |
75 | tx := &Tx{
76 | PoolConn: poolConn,
77 | ID: txID,
78 | Tx: pgTx,
79 | Expires: expireTime,
80 | CancelChan: make(chan bool, 1),
81 | Exited: false,
82 | PoolMu: &sync.Mutex{},
83 | }
84 |
85 | podURL := ""
86 | if utils.POD_URL != "" {
87 | podURL = utils.POD_URL
88 | } else {
89 | podURL = utils.POD_NAME + utils.POD_BASE_DOMAIN
90 | }
91 |
92 | if red.RedisClient != nil {
93 | err = red.SetTransaction(ctx, &red.TransactionMeta{
94 | TxID: txID,
95 | PodID: utils.POD_NAME,
96 | Expiry: expireTime,
97 | PodURL: podURL,
98 | })
99 | if err != nil {
100 | cancel()
101 | return "", fmt.Errorf("error in red.SetTransaction: %w", err)
102 | }
103 | }
104 |
105 | go manager.delayCancelTx(txCtx, cancel, tx.CancelChan, tx.ID)
106 |
107 | manager.txMu.Lock()
108 | defer manager.txMu.Unlock()
109 | manager.txMap[txID] = tx
110 |
111 | return txID, nil
112 | }
113 |
114 | func (manager *TxManager) GetTx(txID string) *Tx {
115 | manager.txMu.Lock()
116 | defer manager.txMu.Unlock()
117 |
118 | tx, exists := manager.txMap[txID]
119 | if !exists {
120 | return nil
121 | }
122 | return tx
123 | }
124 |
125 | // DeleteTx fetches the transaction and removes it from the manager, also sending a signal to cancel the context
126 | func (manager *TxManager) DeleteTx(txID string) error {
127 | manager.txMu.Lock()
128 | defer manager.txMu.Unlock()
129 |
130 | tx, exists := manager.txMap[txID]
131 | if !exists {
132 | return nil
133 | }
134 |
135 | delete(manager.txMap, txID)
136 |
137 | tx.CancelChan <- true
138 |
139 | // TODO: Delete from redis
140 |
141 | return nil
142 | }
143 |
144 | // RollbackTx rolls back the transaction and returns the connection to the pool
145 | func (manager *TxManager) RollbackTx(ctx context.Context, txID string) *DistributedError {
146 | tx := manager.GetTx(txID)
147 | logger := zerolog.Ctx(ctx)
148 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
149 | return c.Str("txID", txID)
150 | })
151 | logger.Debug().Msg("rolling back")
152 | if tx == nil && red.RedisClient != nil {
153 | logger.Debug().Msg("checking for remote transaction for rollback")
154 |
155 | // Check for remote transaction
156 | txMeta, err := red.GetTransaction(ctx, txID)
157 | if errors.Is(err, redis.Nil) {
158 | return &DistributedError{Err: ErrTxNotFound}
159 | }
160 | if err != nil {
161 | return &DistributedError{Err: fmt.Errorf("error in red.GetTransaction: %w", err)}
162 | }
163 |
164 | if txMeta.PodID == utils.POD_NAME {
165 | // The only case would be if this node restarted but maintained the same name, without removing transactions from redis
166 | return &DistributedError{Err: ErrTxNotFoundLocal}
167 | }
168 |
169 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
170 | return c.Str("remoteURL", txMeta.PodURL)
171 | })
172 | logger.Debug().Msg("rolling back on remote")
173 |
174 | // check on remote node
175 | bodyJSON, err := json.Marshal(TxIDJSON{
176 | TxID: txID,
177 | })
178 | if err != nil {
179 | return &DistributedError{Err: fmt.Errorf("error in json.Marhsal for remote request body: %w", err)}
180 | }
181 |
182 | ctx, cancel := context.WithTimeout(ctx, time.Second*30)
183 | defer cancel()
184 |
185 | req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s://%s/psql/rollback", utils.GetHTTPPrefix(), txMeta.PodURL), bytes.NewReader(bodyJSON))
186 | if err != nil {
187 | return &DistributedError{Err: fmt.Errorf("error making http request for remote pod: %w", err)}
188 | }
189 | req.Header.Set("content-type", "application/json")
190 |
191 | res, err := http.DefaultClient.Do(req)
192 | if err != nil {
193 | return &DistributedError{Err: fmt.Errorf("error doing request to remote pod: %w", err)}
194 | }
195 |
196 | resBodyBytes, err := io.ReadAll(res.Body)
197 | if err != nil {
198 | return &DistributedError{Err: fmt.Errorf("error reading body bytes from remote pod response: %w", err)}
199 | }
200 |
201 | if res.StatusCode != 200 {
202 | return &DistributedError{Remote: true, StatusCode: res.StatusCode, ErrString: string(resBodyBytes)}
203 | }
204 |
205 | return nil
206 | } else if tx == nil {
207 | return &DistributedError{Err: ErrTxNotFound}
208 | }
209 |
210 | tx.PoolMu.Lock()
211 | defer tx.PoolMu.Unlock()
212 |
213 | err := tx.Tx.Rollback(ctx)
214 | defer tx.PoolConn.Release()
215 |
216 | if err != nil {
217 | return &DistributedError{Err: fmt.Errorf("error in Tx.Rollback: %w", err)}
218 | }
219 |
220 | err = manager.DeleteTx(txID)
221 | if err != nil {
222 | return &DistributedError{Err: fmt.Errorf("error in manager.DeleteTx: %w", err)}
223 | }
224 |
225 | return nil
226 | }
227 |
228 | // CommitTx commits the transaction and returns the connection to the pool
229 | func (manager *TxManager) CommitTx(ctx context.Context, txID string) *DistributedError {
230 | tx := manager.GetTx(txID)
231 | logger := zerolog.Ctx(ctx)
232 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
233 | return c.Str("txID", txID)
234 | })
235 | logger.Debug().Msg("committing")
236 | if tx == nil && red.RedisClient != nil {
237 | logger.Debug().Msg("checking for remote transaction for commit")
238 |
239 | // Check for remote transaction
240 | txMeta, err := red.GetTransaction(ctx, txID)
241 | if errors.Is(err, redis.Nil) {
242 | return &DistributedError{Err: ErrTxNotFound}
243 | }
244 | if err != nil {
245 | return &DistributedError{Err: fmt.Errorf("error in red.GetTransaction: %w", err)}
246 | }
247 |
248 | if txMeta.PodID == utils.POD_NAME {
249 | // The only case would be if this node restarted but maintained the same name, without removing transactions from redis
250 | return &DistributedError{Err: ErrTxNotFoundLocal}
251 | }
252 |
253 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
254 | return c.Str("remoteURL", txMeta.PodURL)
255 | })
256 | logger.Debug().Msg("committing on remote")
257 |
258 | // check on remote node
259 | bodyJSON, err := json.Marshal(TxIDJSON{
260 | TxID: txID,
261 | })
262 | if err != nil {
263 | return &DistributedError{Err: fmt.Errorf("error in json.Marhsal for remote request body: %w", err)}
264 | }
265 |
266 | ctx, cancel := context.WithTimeout(ctx, time.Second*30)
267 | defer cancel()
268 |
269 | req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s://%s/psql/commit", utils.GetHTTPPrefix(), txMeta.PodURL), bytes.NewReader(bodyJSON))
270 | if err != nil {
271 | return &DistributedError{Err: fmt.Errorf("error making http request for remote pod: %w", err)}
272 | }
273 | req.Header.Set("content-type", "application/json")
274 |
275 | res, err := http.DefaultClient.Do(req)
276 | if err != nil {
277 | return &DistributedError{Err: fmt.Errorf("error doing request to remote pod: %w", err)}
278 | }
279 |
280 | resBodyBytes, err := io.ReadAll(res.Body)
281 | if err != nil {
282 | return &DistributedError{Err: fmt.Errorf("error reading body bytes from remote pod response: %w", err)}
283 | }
284 |
285 | if res.StatusCode != 200 {
286 | return &DistributedError{Remote: true, StatusCode: res.StatusCode, ErrString: string(resBodyBytes)}
287 | }
288 |
289 | return nil
290 | } else if tx == nil {
291 | return &DistributedError{Err: ErrTxNotFound}
292 | }
293 |
294 | tx.PoolMu.Lock()
295 | defer tx.PoolMu.Unlock()
296 |
297 | err := tx.Tx.Commit(ctx)
298 | defer tx.PoolConn.Release()
299 |
300 | if err != nil {
301 | return &DistributedError{Err: fmt.Errorf("error in Tx.Commit: %w", err)}
302 | }
303 |
304 | err = manager.DeleteTx(txID)
305 | if err != nil {
306 | return &DistributedError{Err: fmt.Errorf("error in manager.DeleteTx: %w", err)}
307 | }
308 |
309 | return nil
310 | }
311 |
312 | func (manager *TxManager) delayCancelTx(ctx context.Context, cancel context.CancelFunc, cancelChan chan bool, txID string) {
313 | select {
314 | case <-cancelChan:
315 | logger.Debug().Msgf("cancelling context for transaction %s", txID)
316 | cancel()
317 | case <-ctx.Done():
318 | logger.Debug().Msgf("context cancelled for transaction %s", txID)
319 | break
320 | }
321 | }
322 |
323 | // handleExpiredTransaction should be run in a goroutine
324 | func (manager *TxManager) handleExpiredTransactions() {
325 | logger.Debug().Msg("looking for expired transactions")
326 | expireTime := time.Now()
327 | expiredTXIDs := make([]string, 0)
328 | manager.txMu.Lock()
329 | for id, tx := range manager.txMap {
330 | if tx.Expires.Before(expireTime) {
331 | expiredTXIDs = append(expiredTXIDs, id)
332 | }
333 | }
334 | manager.txMu.Unlock()
335 |
336 | if len(expiredTXIDs) == 0 {
337 | logger.Debug().Msg("found no expired transactions")
338 | return
339 | }
340 |
341 | // Expire the IDs
342 | logger.Debug().Msgf("Got %d transactions to expire", len(expiredTXIDs))
343 | for _, txID := range expiredTXIDs {
344 | logger.Debug().Msgf("expiring transaction %s", txID)
345 | // We will wait forever to try and handle it
346 | err := manager.RollbackTx(context.Background(), txID)
347 | if err != nil {
348 | logger.Error().Err(err.Err).Msgf("error rolling back transaction %s", txID)
349 | } else {
350 | logger.Debug().Msgf("expired transaction %s", txID)
351 | }
352 | }
353 | }
354 |
355 | func (manager *TxManager) Shutdown() {
356 | manager.tickerStopChan <- true
357 | // We do wait for all HTTP requests to end before doing this
358 | // TODO: Remove all transactions from redis in case this gets the same name
359 | }
360 |
--------------------------------------------------------------------------------
/pg/tx_test.go:
--------------------------------------------------------------------------------
1 | package pg
2 |
3 | import (
4 | "context"
5 | "github.com/danthegoodman1/SQLGateway/red"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func TestTransactionStorage(t *testing.T) {
11 | txMeta := &red.TransactionMeta{
12 | TxID: "test",
13 | PodID: "testpod",
14 | Expiry: time.Now().Add(time.Second * 10),
15 | PodURL: "localhost:8080",
16 | }
17 |
18 | err := red.SetTransaction(context.Background(), txMeta)
19 | if err != nil {
20 | t.Fatal(err)
21 | }
22 |
23 | txBack, err := red.GetTransaction(context.Background(), txMeta.TxID)
24 | if err != nil {
25 | t.Fatal(err)
26 | }
27 |
28 | if txBack.TxID != txMeta.TxID {
29 | t.Fatalf("transactions not the same: %+v %+v", txBack, txMeta)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/red/red.go:
--------------------------------------------------------------------------------
1 | package red
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "errors"
7 | "fmt"
8 | "github.com/danthegoodman1/SQLGateway/gologger"
9 | "github.com/danthegoodman1/SQLGateway/utils"
10 | "github.com/go-redis/redis/v9"
11 | "github.com/rs/zerolog"
12 | "time"
13 | )
14 |
15 | var (
16 | RedisClient *redis.Client
17 | BGStopChan = make(chan bool, 1)
18 | Ticker = time.NewTicker(time.Second * 5)
19 | logger = gologger.NewLogger()
20 |
21 | ErrTxAlreadyExists = errors.New("transaction already exists")
22 | )
23 |
24 | type (
25 | Peer struct {
26 | PodName string
27 | LastUpdate time.Time
28 | }
29 |
30 | TransactionMeta struct {
31 | TxID string
32 | PodID string
33 | PodURL string
34 | Expiry time.Time
35 | }
36 | )
37 |
38 | func ConnectRedis() error {
39 | logger.Debug().Msg("connecting to redis")
40 | if utils.REDIS_ADDR != "" {
41 | redisOpts := &redis.Options{
42 | //Addrs: []string{utils.REDIS_ADDR},
43 | Addr: utils.REDIS_ADDR,
44 | DialTimeout: time.Second * 10,
45 | PoolSize: int(utils.REDIS_POOL_CONNS),
46 |
47 | // For a single cluster mode endpoint
48 | //RouteRandomly: false,
49 | //ReadOnly: false,
50 | //RouteByLatency: false,
51 | }
52 | if utils.REDIS_PASSWORD != "" {
53 | redisOpts.Password = utils.REDIS_PASSWORD
54 | }
55 |
56 | RedisClient = redis.NewClient(redisOpts)
57 |
58 | // Test connection
59 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
60 | defer cancel()
61 |
62 | _, err := RedisClient.Ping(ctx).Result()
63 | if err != nil {
64 | return fmt.Errorf("error in RedisClient.Ping: %w", err)
65 | }
66 | logger.Debug().Msg("connected to redis")
67 | }
68 | //go func() {
69 | // logger.Debug().Msg("starting redis background worker")
70 | // for {
71 | // select {
72 | // case <-Ticker.C:
73 | // go updateRedisSD()
74 | // case <-BGStopChan:
75 | // return
76 | // }
77 | // }
78 | //}()
79 | return nil
80 | }
81 |
82 | // getSelfPeerJSONBytes Gets the *Peer of this pod as JSON bytes
83 | func getSelfPeerJSONBytes() ([]byte, error) {
84 | peer := &Peer{
85 | PodName: utils.POD_NAME,
86 | LastUpdate: time.Now(),
87 | }
88 |
89 | jsonBytes, err := json.Marshal(peer)
90 | if err != nil {
91 | return nil, fmt.Errorf("error in json.Marshal: %w", err)
92 | }
93 | return jsonBytes, nil
94 | }
95 |
96 | func peerFromBytes(jsonBytes []byte) (*Peer, error) {
97 | var peer Peer
98 | err := json.Unmarshal(jsonBytes, &peer)
99 | if err != nil {
100 | return nil, fmt.Errorf("error in json.Unmarshal: %w", err)
101 | }
102 | return &peer, nil
103 | }
104 |
105 | // updateRedisSD should be launched in a go routine
106 | //func updateRedisSD() {
107 | // logger.Debug().Msg("updating Redis SD")
108 | // ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
109 | // defer cancel()
110 | //
111 | // self, err := getSelfPeerJSONBytes()
112 | // if err != nil {
113 | // logger.Error().Err(err).Msg("error getting self peer json bytes")
114 | // return
115 | // }
116 | //
117 | // s := time.Now()
118 | // _, err = RedisClient.HSet(ctx, utils.V_NAMESPACE, utils.POD_NAME, string(self)).Result()
119 | // if err != nil {
120 | // logger.Error().Err(err).Msg("error in RedisClient.HSET")
121 | // return
122 | // }
123 | // since := time.Since(s)
124 | // logger.Debug().Int64("updateTimeNS", since.Nanoseconds()).Msgf("updated Redis SD in %s", since)
125 | //}
126 |
127 | func GetPeers(ctx context.Context) (map[string]*Peer, error) {
128 | logger := zerolog.Ctx(ctx)
129 |
130 | ctx, cancel := context.WithTimeout(ctx, time.Second*5)
131 | defer cancel()
132 |
133 | s := time.Now()
134 | rHash, err := RedisClient.HGetAll(ctx, utils.V_NAMESPACE).Result()
135 | if err != nil {
136 | return nil, fmt.Errorf("error in RedisClient.HGetAll: %w", err)
137 | }
138 |
139 | since := time.Since(s)
140 | logger.Debug().Int64("updateTimeNS", since.Nanoseconds()).Msgf("got peers from redis in %s", since)
141 |
142 | peers := make(map[string]*Peer, 0)
143 | for podName, peerJSON := range rHash {
144 | peer, err := peerFromBytes([]byte(peerJSON))
145 | if err != nil {
146 | return nil, fmt.Errorf("error in peerFromBytes: %w", err)
147 | }
148 | peers[podName] = peer
149 | }
150 | return peers, nil
151 | }
152 |
153 | func SetTransaction(ctx context.Context, txMeta *TransactionMeta) error {
154 | logger := zerolog.Ctx(ctx)
155 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
156 | return c.Str("txID", txMeta.TxID)
157 | })
158 | logger.Debug().Msg("setting transaction in redis")
159 | s := time.Now()
160 |
161 | txMetaBytes, err := json.Marshal(txMeta)
162 | if err != nil {
163 | return fmt.Errorf("error in json.Marshal: %w", err)
164 | }
165 |
166 | set, err := RedisClient.SetNX(ctx, txMeta.TxID, string(txMetaBytes), txMeta.Expiry.Sub(time.Now())).Result()
167 | if err != nil {
168 | return fmt.Errorf("error in RedisClient.SetNX: %w", err)
169 | }
170 | if !set {
171 | return ErrTxAlreadyExists
172 | }
173 | if utils.TRACES {
174 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
175 | return c.Interface("set_transaction", gologger.Action{
176 | DurationNS: time.Since(s).Nanoseconds(),
177 | })
178 | })
179 | }
180 | return nil
181 | }
182 |
183 | func GetTransaction(ctx context.Context, txID string) (txMeta *TransactionMeta, err error) {
184 | logger := zerolog.Ctx(ctx)
185 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
186 | return c.Str("txID", txID)
187 | })
188 | logger.Debug().Msg("getting transaction from redis")
189 | s := time.Now()
190 | txString, err := RedisClient.Get(ctx, txID).Result()
191 | if err != nil {
192 | return nil, fmt.Errorf("error in RedisClient.Get: %w", err)
193 | }
194 |
195 | err = json.Unmarshal([]byte(txString), &txMeta)
196 | if err != nil {
197 | return nil, fmt.Errorf("error in json.Unmarshal: %w", err)
198 | }
199 | if utils.TRACES {
200 | logger.UpdateContext(func(c zerolog.Context) zerolog.Context {
201 | return c.Interface("get_transaction", gologger.Action{
202 | DurationNS: time.Since(s).Nanoseconds(),
203 | })
204 | })
205 | }
206 | return
207 | }
208 |
209 | func Shutdown(ctx context.Context) error {
210 | logger.Debug().Msg("shutting down redis client")
211 |
212 | // Stop the background poller
213 | //BGStopChan <- true
214 |
215 | // Remove the pod from the cluster
216 | _, err := RedisClient.HDel(ctx, utils.V_NAMESPACE, utils.POD_NAME).Result()
217 | if err != nil {
218 | return fmt.Errorf("error in RedisClient.HDel(): %w", err)
219 | }
220 |
221 | err = RedisClient.Close()
222 | if err != nil {
223 | return fmt.Errorf("error in RedisClient.Close(): %w", err)
224 | }
225 |
226 | return nil
227 | }
228 |
--------------------------------------------------------------------------------
/test.http:
--------------------------------------------------------------------------------
1 | ### Health check
2 | GET http://localhost:8080/hc
3 |
4 | ### Run Query
5 | POST http://localhost:8080/psql/query
6 | content-type: application/json
7 |
8 | {
9 | "Queries": [
10 | {
11 | "Statement": "SELECT $1::INT8 as a_number",
12 | "Params": [
13 | 42
14 | ]
15 | }
16 | ]
17 | }
--------------------------------------------------------------------------------
/utils/env.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "os"
4 |
5 | var (
6 | PG_DSN = os.Getenv("PG_DSN")
7 | PG_POOL_CONNS = GetEnvOrDefaultInt("PG_POOL_CONNS", 2)
8 |
9 | REDIS_ADDR = os.Getenv("REDIS_ADDR")
10 | REDIS_PASSWORD = os.Getenv("REDIS_PASSWORD")
11 | REDIS_POOL_CONNS = GetEnvOrDefaultInt("REDIS_POOL_CONNS", 2)
12 |
13 | // V_NAMESPACE Virtual namespace for redis hash map name
14 | V_NAMESPACE = os.Getenv("V_NAMESPACE")
15 |
16 | // this pod can be reached at url: {POD_NAME}{POD_BASE_DOMAIN}
17 | POD_NAME = os.Getenv("POD_NAME")
18 | // this pod can be reached at url: {POD_NAME}{POD_BASE_DOMAIN}
19 | POD_BASE_DOMAIN = os.Getenv("POD_BASE_DOMAIN")
20 | // overrides {POD_NAME}{POD_BASE_DOMAIN} to advertise {POD_URL}
21 | POD_URL = os.Getenv("POD_URL")
22 |
23 | HTTP_PORT = GetEnvOrDefault("HTTP_PORT", "8080")
24 |
25 | // Whether to use https for inter-pod communication, defaults to false
26 | POD_HTTPS = os.Getenv("POD_HTTPS") == "1"
27 |
28 | // Whether to emit traces in the HTTP logs
29 | TRACES = os.Getenv("TRACES") == "1"
30 |
31 | AUTH_USER = os.Getenv("AUTH_USER")
32 | AUTH_PASS = os.Getenv("AUTH_PASS")
33 | )
34 |
--------------------------------------------------------------------------------
/utils/errors.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | type PermError string
4 |
5 | func (e PermError) Error() string {
6 | return string(e)
7 | }
8 |
9 | func (e PermError) IsPermanent() bool {
10 | return true
11 | }
12 |
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "context"
5 | "database/sql"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "net/http"
10 | "os"
11 | "strconv"
12 | "strings"
13 | "time"
14 |
15 | "github.com/jackc/pgtype"
16 |
17 | "github.com/cockroachdb/cockroach-go/v2/crdb/crdbpgx"
18 | "github.com/danthegoodman1/SQLGateway/gologger"
19 | "github.com/labstack/echo/v4"
20 | gonanoid "github.com/matoous/go-nanoid/v2"
21 | "github.com/rs/zerolog"
22 | "github.com/segmentio/ksuid"
23 |
24 | "github.com/UltimateTournament/backoff/v4"
25 | "github.com/jackc/pgconn"
26 | "github.com/jackc/pgx/v4"
27 | "github.com/jackc/pgx/v4/pgxpool"
28 | )
29 |
30 | var logger = gologger.NewLogger()
31 |
32 | func GetEnvOrDefault(env, defaultVal string) string {
33 | e := os.Getenv(env)
34 | if e == "" {
35 | return defaultVal
36 | } else {
37 | return e
38 | }
39 | }
40 |
41 | func GetEnvOrDefaultInt(env string, defaultVal int64) int64 {
42 | e := os.Getenv(env)
43 | if e == "" {
44 | return defaultVal
45 | } else {
46 | intVal, err := strconv.ParseInt(e, 10, 16)
47 | if err != nil {
48 | logger.Error().Msg(fmt.Sprintf("Failed to parse string to int '%s'", env))
49 | os.Exit(1)
50 | }
51 |
52 | return (intVal)
53 | }
54 | }
55 |
56 | func GenRandomID(prefix string) string {
57 | return prefix + gonanoid.MustGenerate("abcdefghijklmonpqrstuvwxyzABCDEFGHIJKLMONPQRSTUVWXYZ0123456789", 22)
58 | }
59 |
60 | func GenKSortedID(prefix string) string {
61 | return prefix + ksuid.New().String()
62 | }
63 |
64 | // Cannot use to set something to "", must manually use sq.NullString for that
65 | func SQLNullString(s string) sql.NullString {
66 | return sql.NullString{
67 | String: s,
68 | Valid: s != "",
69 | }
70 | }
71 |
72 | func SQLNullStringP(s *string) sql.NullString {
73 | return sql.NullString{
74 | String: Deref(s, ""),
75 | Valid: s != nil,
76 | }
77 | }
78 |
79 | func SQLNullInt64P(s *int64) sql.NullInt64 {
80 | return sql.NullInt64{
81 | Int64: Deref(s, 0),
82 | Valid: s != nil,
83 | }
84 | }
85 |
86 | func SQLNullBoolP(s *bool) sql.NullBool {
87 | return sql.NullBool{
88 | Bool: Deref(s, false),
89 | Valid: s != nil,
90 | }
91 | }
92 |
93 | // Cannot use to set something to 0, must manually use sq.NullInt64 for that
94 | func SQLNullInt64(s int64) sql.NullInt64 {
95 | return sql.NullInt64{
96 | Int64: s,
97 | Valid: true,
98 | }
99 | }
100 |
101 | func GenRandomShortID() string {
102 | // reduced character set that's less probable to mis-type
103 | // change for conflicts is still only 1:128 trillion
104 | return gonanoid.MustGenerate("abcdefghikmonpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789", 8)
105 | }
106 |
107 | func DaysUntil(t time.Time, d time.Weekday) int {
108 | delta := d - t.Weekday()
109 | if delta < 0 {
110 | delta += 7
111 | }
112 | return int(delta)
113 | }
114 |
115 | // this wrapper exists so caller stack skipping works
116 | func ReliableExec(ctx context.Context, pool *pgxpool.Pool, tryTimeout time.Duration, f func(ctx context.Context, conn *pgxpool.Conn) error) error {
117 | return reliableExec(ctx, pool, tryTimeout, func(ctx context.Context, tx *pgxpool.Conn) error {
118 | return f(ctx, tx)
119 | })
120 | }
121 |
122 | func ReliableExecInTx(ctx context.Context, pool *pgxpool.Pool, tryTimeout time.Duration, f func(ctx context.Context, conn pgx.Tx) error) error {
123 | return reliableExec(ctx, pool, tryTimeout, func(ctx context.Context, tx *pgxpool.Conn) error {
124 | return crdbpgx.ExecuteTx(ctx, tx, pgx.TxOptions{}, func(tx pgx.Tx) error {
125 | return f(ctx, tx)
126 | })
127 | })
128 | }
129 |
130 | func reliableExec(ctx context.Context, pool *pgxpool.Pool, tryTimeout time.Duration, f func(ctx context.Context, conn *pgxpool.Conn) error) error {
131 | cfg := backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 3)
132 |
133 | return backoff.RetryNotify(func() error {
134 | conn, err := pool.Acquire(ctx)
135 | if err != nil {
136 | if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
137 | return backoff.Permanent(err)
138 | }
139 | return err
140 | }
141 | defer conn.Release()
142 | tryCtx, cancel := context.WithTimeout(ctx, tryTimeout)
143 | defer cancel()
144 | err = f(tryCtx, conn)
145 | if errors.Is(err, pgx.ErrNoRows) {
146 | return backoff.Permanent(err)
147 | }
148 | if IsPermSQLErr(err) {
149 | return backoff.Permanent(err)
150 | }
151 | if IsRelationDoesNotExist(err) {
152 | return backoff.Permanent(err)
153 | }
154 | if IsUniqueConstraint(err) {
155 | return backoff.Permanent(err)
156 | }
157 | if IsSyntaxError(err) {
158 | return backoff.Permanent(err)
159 | }
160 | // not context.DeadlineExceeded as that's expected due to `tryTimeout`
161 | if errors.Is(err, context.Canceled) {
162 | return backoff.Permanent(err)
163 | }
164 | return err
165 | }, cfg, func(err error, d time.Duration) {
166 | reqID, _ := ctx.Value(gologger.ReqIDKey).(string)
167 | l := zerolog.Ctx(ctx).Info().Err(err).CallerSkipFrame(5)
168 | if reqID != "" {
169 | l.Str(string(gologger.ReqIDKey), reqID)
170 | }
171 | l.Msg("ReliableExec retrying")
172 | })
173 | }
174 |
175 | func IsPermSQLErr(err error) bool {
176 | if err == nil {
177 | return false
178 | }
179 | var pgErr *pgconn.PgError
180 | if errors.As(err, &pgErr) {
181 | if pgErr.Code == "23505" {
182 | // This is a duplicate key - unique constraint
183 | return true
184 | }
185 | if pgErr.Code == "42703" {
186 | // Column does not exist
187 | return true
188 | }
189 | }
190 | return false
191 | }
192 |
193 | func IsUniqueConstraint(err error) bool {
194 | if err == nil {
195 | return false
196 | }
197 | var pgErr *pgconn.PgError
198 | if errors.As(err, &pgErr) {
199 | if pgErr.Code == "23505" {
200 | // This is a duplicate key - unique constraint
201 | return true
202 | }
203 | }
204 | return false
205 | }
206 |
207 | func IsRelationDoesNotExist(err error) bool {
208 | if err == nil {
209 | return false
210 | }
211 | var pgErr *pgconn.PgError
212 | if errors.As(err, &pgErr) {
213 | if pgErr.Code == "42P01" {
214 | // This is a duplicate key - unique constraint
215 | return true
216 | }
217 | }
218 | return false
219 | }
220 |
221 | func IsSyntaxError(err error) bool {
222 | if err == nil {
223 | return false
224 | }
225 | var pgErr *pgconn.PgError
226 | if errors.As(err, &pgErr) {
227 | if pgErr.Code == "42601" {
228 | return true
229 | }
230 | }
231 | return false
232 | }
233 |
234 | func Ptr[T any](s T) *T {
235 | return &s
236 | }
237 |
238 | type NoEscapeJSONSerializer struct{}
239 |
240 | var _ echo.JSONSerializer = &NoEscapeJSONSerializer{}
241 |
242 | func (d *NoEscapeJSONSerializer) Serialize(c echo.Context, i interface{}, indent string) error {
243 | enc := json.NewEncoder(c.Response())
244 | enc.SetEscapeHTML(false)
245 | if indent != "" {
246 | enc.SetIndent("", indent)
247 | }
248 | return enc.Encode(i)
249 | }
250 |
251 | // Deserialize reads a JSON from a request body and converts it into an interface.
252 | func (d *NoEscapeJSONSerializer) Deserialize(c echo.Context, i interface{}) error {
253 | // Does not escape <, >, and ?
254 | err := json.NewDecoder(c.Request().Body).Decode(i)
255 | var ute *json.UnmarshalTypeError
256 | var se *json.SyntaxError
257 | if ok := errors.As(err, &ute); ok {
258 | return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
259 | } else if ok := errors.As(err, &se); ok {
260 | return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
261 | }
262 | return err
263 | }
264 |
265 | func Deref[T any](ref *T, fallback T) T {
266 | if ref == nil {
267 | return fallback
268 | }
269 | return *ref
270 | }
271 |
272 | func ArrayOrEmpty[T any](ref []T) []T {
273 | if ref == nil {
274 | return make([]T, 0)
275 | }
276 | return ref
277 | }
278 |
279 | var emptyJSON = pgtype.JSONB{Bytes: []byte("{}"), Status: pgtype.Present}
280 |
281 | func OrEmptyJSON(data pgtype.JSONB) pgtype.JSONB {
282 | if data.Status == pgtype.Null {
283 | data = emptyJSON
284 | }
285 | return data
286 | }
287 |
288 | func IfElse[T any](check bool, a T, b T) T {
289 | if check {
290 | return a
291 | }
292 | return b
293 | }
294 |
295 | func OrEmptyArray[T any](a []T) []T {
296 | if a == nil {
297 | return make([]T, 0)
298 | }
299 | return a
300 | }
301 |
302 | func FirstOr[T any](a []T, def T) T {
303 | if len(a) == 0 {
304 | return def
305 | }
306 | return a[0]
307 | }
308 |
309 | var ErrVersionBadFormat = PermError("bad version format")
310 |
311 | // VersionToInt converts a simple semantic version string (e.e. 18.02.66)
312 | func VersionToInt(v string) (int64, error) {
313 | sParts := strings.Split(v, ".")
314 | if len(sParts) > 3 {
315 | return -1, ErrVersionBadFormat
316 | }
317 | var iParts = make([]int64, 3)
318 | for i := range sParts {
319 | vp, err := strconv.ParseInt(sParts[i], 10, 64)
320 | if err != nil {
321 | return -1, fmt.Errorf("error in ParseInt: %s %w", err.Error(), ErrVersionBadFormat)
322 | }
323 | iParts[i] = vp
324 | }
325 | return iParts[0]*10_000*10_000 + iParts[1]*10_000 + iParts[2], nil
326 | }
327 |
328 | func GetHTTPPrefix() string {
329 | if POD_HTTPS {
330 | return "https"
331 | }
332 | return "http"
333 | }
334 |
--------------------------------------------------------------------------------