├── .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 | --------------------------------------------------------------------------------