diff --git a/api/config.go b/api/config.go index 2275dd1..2cce93e 100644 --- a/api/config.go +++ b/api/config.go @@ -24,7 +24,7 @@ type getConfigResponse struct { } func getConfig(ctx *gin.Context) { - // swagger:route GET /config ConfigManagement getConfigRequest + // swagger:route GET /config Config getConfigRequest // // Get current config. // @@ -71,7 +71,7 @@ type saveConfigResponse struct { } func saveConfig(ctx *gin.Context) { - // swagger:route POST /config ConfigManagement saveConfigRequest + // swagger:route POST /config Config saveConfigRequest // // Save current config to file (gost.yaml or gost.json). // diff --git a/api/config_admission.go b/api/config_admission.go index 31fce1e..9faa315 100644 --- a/api/config_admission.go +++ b/api/config_admission.go @@ -22,7 +22,7 @@ type createAdmissionResponse struct { } func createAdmission(ctx *gin.Context) { - // swagger:route POST /config/admissions ConfigManagement createAdmissionRequest + // swagger:route POST /config/admissions Admission createAdmissionRequest // // Create a new admission, the name of admission must be unique in admission list. // @@ -72,7 +72,7 @@ type updateAdmissionResponse struct { } func updateAdmission(ctx *gin.Context) { - // swagger:route PUT /config/admissions/{admission} ConfigManagement updateAdmissionRequest + // swagger:route PUT /config/admissions/{admission} Admission updateAdmissionRequest // // Update admission by name, the admission must already exist. // @@ -130,7 +130,7 @@ type deleteAdmissionResponse struct { } func deleteAdmission(ctx *gin.Context) { - // swagger:route DELETE /config/admissions/{admission} ConfigManagement deleteAdmissionRequest + // swagger:route DELETE /config/admissions/{admission} Admission deleteAdmissionRequest // // Delete admission by name. // diff --git a/api/config_auther.go b/api/config_auther.go index e17601e..53e1048 100644 --- a/api/config_auther.go +++ b/api/config_auther.go @@ -22,7 +22,7 @@ type createAutherResponse struct { } func createAuther(ctx *gin.Context) { - // swagger:route POST /config/authers ConfigManagement createAutherRequest + // swagger:route POST /config/authers Auther createAutherRequest // // Create a new auther, the name of the auther must be unique in auther list. // @@ -71,7 +71,7 @@ type updateAutherResponse struct { } func updateAuther(ctx *gin.Context) { - // swagger:route PUT /config/authers/{auther} ConfigManagement updateAutherRequest + // swagger:route PUT /config/authers/{auther} Auther updateAutherRequest // // Update auther by name, the auther must already exist. // @@ -128,7 +128,7 @@ type deleteAutherResponse struct { } func deleteAuther(ctx *gin.Context) { - // swagger:route DELETE /config/authers/{auther} ConfigManagement deleteAutherRequest + // swagger:route DELETE /config/authers/{auther} Auther deleteAutherRequest // // Delete auther by name. // diff --git a/api/config_bypass.go b/api/config_bypass.go index 4defc98..b84539b 100644 --- a/api/config_bypass.go +++ b/api/config_bypass.go @@ -22,7 +22,7 @@ type createBypassResponse struct { } func createBypass(ctx *gin.Context) { - // swagger:route POST /config/bypasses ConfigManagement createBypassRequest + // swagger:route POST /config/bypasses Bypass createBypassRequest // // Create a new bypass, the name of bypass must be unique in bypass list. // @@ -72,7 +72,7 @@ type updateBypassResponse struct { } func updateBypass(ctx *gin.Context) { - // swagger:route PUT /config/bypasses/{bypass} ConfigManagement updateBypassRequest + // swagger:route PUT /config/bypasses/{bypass} Bypass updateBypassRequest // // Update bypass by name, the bypass must already exist. // @@ -130,7 +130,7 @@ type deleteBypassResponse struct { } func deleteBypass(ctx *gin.Context) { - // swagger:route DELETE /config/bypasses/{bypass} ConfigManagement deleteBypassRequest + // swagger:route DELETE /config/bypasses/{bypass} Bypass deleteBypassRequest // // Delete bypass by name. // diff --git a/api/config_chain.go b/api/config_chain.go index 741411a..3b8a728 100644 --- a/api/config_chain.go +++ b/api/config_chain.go @@ -22,7 +22,7 @@ type createChainResponse struct { } func createChain(ctx *gin.Context) { - // swagger:route POST /config/chains ConfigManagement createChainRequest + // swagger:route POST /config/chains Chain createChainRequest // // Create a new chain, the name of chain must be unique in chain list. // @@ -77,7 +77,7 @@ type updateChainResponse struct { } func updateChain(ctx *gin.Context) { - // swagger:route PUT /config/chains/{chain} ConfigManagement updateChainRequest + // swagger:route PUT /config/chains/{chain} Chain updateChainRequest // // Update chain by name, the chain must already exist. // @@ -139,7 +139,7 @@ type deleteChainResponse struct { } func deleteChain(ctx *gin.Context) { - // swagger:route DELETE /config/chains/{chain} ConfigManagement deleteChainRequest + // swagger:route DELETE /config/chains/{chain} Chain deleteChainRequest // // Delete chain by name. // diff --git a/api/config_hosts.go b/api/config_hosts.go index 4d5c78d..9b7d7e2 100644 --- a/api/config_hosts.go +++ b/api/config_hosts.go @@ -22,7 +22,7 @@ type createHostsesponse struct { } func createHosts(ctx *gin.Context) { - // swagger:route POST /config/hosts ConfigManagement createHostsRequest + // swagger:route POST /config/hosts Hosts createHostsRequest // // Create a new hosts, the name of the hosts must be unique in hosts list. // @@ -72,7 +72,7 @@ type updateHostsResponse struct { } func updateHosts(ctx *gin.Context) { - // swagger:route PUT /config/hosts/{hosts} ConfigManagement updateHostsRequest + // swagger:route PUT /config/hosts/{hosts} Hosts updateHostsRequest // // Update hosts by name, the hosts must already exist. // @@ -130,7 +130,7 @@ type deleteHostsResponse struct { } func deleteHosts(ctx *gin.Context) { - // swagger:route DELETE /config/hosts/{hosts} ConfigManagement deleteHostsRequest + // swagger:route DELETE /config/hosts/{hosts} Hosts deleteHostsRequest // // Delete hosts by name. // diff --git a/api/config_limiter.go b/api/config_limiter.go new file mode 100644 index 0000000..c3abfd5 --- /dev/null +++ b/api/config_limiter.go @@ -0,0 +1,166 @@ +package api + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-gost/x/config" + "github.com/go-gost/x/config/parsing" + "github.com/go-gost/x/registry" +) + +// swagger:parameters createLimiterRequest +type createLimiterRequest struct { + // in: body + Data config.LimiterConfig `json:"data"` +} + +// successful operation. +// swagger:response createLimiterResponse +type createLimiterResponse struct { + Data Response +} + +func createLimiter(ctx *gin.Context) { + // swagger:route POST /config/limiters Limiter createLimiterRequest + // + // Create a new limiter, the name of limiter must be unique in limiter list. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: createLimiterResponse + + var req createLimiterRequest + ctx.ShouldBindJSON(&req.Data) + + if req.Data.Name == "" { + writeError(ctx, ErrInvalid) + return + } + + v := parsing.ParseRateLimiter(&req.Data) + + if err := registry.RateLimiterRegistry().Register(req.Data.Name, v); err != nil { + writeError(ctx, ErrDup) + return + } + + cfg := config.Global() + cfg.Limiters = append(cfg.Limiters, &req.Data) + config.SetGlobal(cfg) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters updateLimiterRequest +type updateLimiterRequest struct { + // in: path + // required: true + Limiter string `uri:"limiter" json:"limiter"` + // in: body + Data config.LimiterConfig `json:"data"` +} + +// successful operation. +// swagger:response updateLimiterResponse +type updateLimiterResponse struct { + Data Response +} + +func updateLimiter(ctx *gin.Context) { + // swagger:route PUT /config/limiters/{limiter} Limiter updateLimiterRequest + // + // Update limiter by name, the limiter must already exist. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: updateLimiterResponse + + var req updateLimiterRequest + ctx.ShouldBindUri(&req) + ctx.ShouldBindJSON(&req.Data) + + if !registry.RateLimiterRegistry().IsRegistered(req.Limiter) { + writeError(ctx, ErrNotFound) + return + } + + req.Data.Name = req.Limiter + + v := parsing.ParseRateLimiter(&req.Data) + + registry.RateLimiterRegistry().Unregister(req.Limiter) + + if err := registry.RateLimiterRegistry().Register(req.Limiter, v); err != nil { + writeError(ctx, ErrDup) + return + } + + cfg := config.Global() + for i := range cfg.Limiters { + if cfg.Limiters[i].Name == req.Limiter { + cfg.Limiters[i] = &req.Data + break + } + } + config.SetGlobal(cfg) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} + +// swagger:parameters deleteLimiterRequest +type deleteLimiterRequest struct { + // in: path + // required: true + Limiter string `uri:"Limiter" json:"Limiter"` +} + +// successful operation. +// swagger:response deleteLimiterResponse +type deleteLimiterResponse struct { + Data Response +} + +func deleteLimiter(ctx *gin.Context) { + // swagger:route DELETE /config/limiters/{limiter} Limiter deleteLimiterRequest + // + // Delete limiter by name. + // + // Security: + // basicAuth: [] + // + // Responses: + // 200: deleteLimiterResponse + + var req deleteLimiterRequest + ctx.ShouldBindUri(&req) + + if !registry.RateLimiterRegistry().IsRegistered(req.Limiter) { + writeError(ctx, ErrNotFound) + return + } + registry.RateLimiterRegistry().Unregister(req.Limiter) + + cfg := config.Global() + limiteres := cfg.Limiters + cfg.Limiters = nil + for _, s := range limiteres { + if s.Name == req.Limiter { + continue + } + cfg.Limiters = append(cfg.Limiters, s) + } + config.SetGlobal(cfg) + + ctx.JSON(http.StatusOK, Response{ + Msg: "OK", + }) +} diff --git a/api/config_resolver.go b/api/config_resolver.go index 80bf999..f09ff8f 100644 --- a/api/config_resolver.go +++ b/api/config_resolver.go @@ -22,7 +22,7 @@ type createResolverResponse struct { } func createResolver(ctx *gin.Context) { - // swagger:route POST /config/resolvers ConfigManagement createResolverRequest + // swagger:route POST /config/resolvers Resolver createResolverRequest // // Create a new resolver, the name of the resolver must be unique in resolver list. // @@ -76,7 +76,7 @@ type updateResolverResponse struct { } func updateResolver(ctx *gin.Context) { - // swagger:route PUT /config/resolvers/{resolver} ConfigManagement updateResolverRequest + // swagger:route PUT /config/resolvers/{resolver} Resolver updateResolverRequest // // Update resolver by name, the resolver must already exist. // @@ -138,7 +138,7 @@ type deleteResolverResponse struct { } func deleteResolver(ctx *gin.Context) { - // swagger:route DELETE /config/resolvers/{resolver} ConfigManagement deleteResolverRequest + // swagger:route DELETE /config/resolvers/{resolver} Resolver deleteResolverRequest // // Delete resolver by name. // diff --git a/api/config_service.go b/api/config_service.go index 9d80e82..9a268c8 100644 --- a/api/config_service.go +++ b/api/config_service.go @@ -22,7 +22,7 @@ type createServiceResponse struct { } func createService(ctx *gin.Context) { - // swagger:route POST /config/services ConfigManagement createServiceRequest + // swagger:route POST /config/services Service createServiceRequest // // Create a new service, the name of the service must be unique in service list. // @@ -84,7 +84,7 @@ type updateServiceResponse struct { } func updateService(ctx *gin.Context) { - // swagger:route PUT /config/services/{service} ConfigManagement updateServiceRequest + // swagger:route PUT /config/services/{service} Service updateServiceRequest // // Update service by name, the service must already exist. // @@ -151,7 +151,7 @@ type deleteServiceResponse struct { } func deleteService(ctx *gin.Context) { - // swagger:route DELETE /config/services/{service} ConfigManagement deleteServiceRequest + // swagger:route DELETE /config/services/{service} Service deleteServiceRequest // // Delete service by name. // diff --git a/api/service.go b/api/service.go index ec44ab8..bf41ba0 100644 --- a/api/service.go +++ b/api/service.go @@ -129,4 +129,9 @@ func registerConfig(config *gin.RouterGroup) { config.POST("/hosts", createHosts) config.PUT("/hosts/:hosts", updateHosts) config.DELETE("/hosts/:hosts", deleteHosts) + + config.POST("/limiters", createLimiter) + config.PUT("/limiters/:limiter", updateLimiter) + config.DELETE("/limiters/:limiter", deleteLimiter) + } diff --git a/api/swagger.yaml b/api/swagger.yaml index b0310d9..03968ef 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -156,6 +156,11 @@ definitions: $ref: '#/definitions/HostsConfig' type: array x-go-name: Hosts + limiters: + items: + $ref: '#/definitions/LimiterConfig' + type: array + x-go-name: Limiters log: $ref: '#/definitions/LogConfig' metrics: @@ -351,6 +356,15 @@ definitions: $ref: '#/definitions/Duration' type: object x-go-package: github.com/go-gost/x/config + LimiterConfig: + properties: + name: + type: string + x-go-name: Name + rate: + $ref: '#/definitions/RateLimiterConfig' + type: object + x-go-package: github.com/go-gost/x/config ListenerConfig: properties: auth: @@ -469,6 +483,21 @@ definitions: x-go-name: Addr type: object x-go-package: github.com/go-gost/x/config + RateLimiterConfig: + properties: + file: + $ref: '#/definitions/FileLoader' + limits: + items: + type: string + type: array + x-go-name: Limits + redis: + $ref: '#/definitions/RedisLoader' + reload: + $ref: '#/definitions/Duration' + type: object + x-go-package: github.com/go-gost/x/config RecorderConfig: properties: file: @@ -597,6 +626,9 @@ definitions: interface: type: string x-go-name: Interface + limiter: + type: string + x-go-name: Limiter listener: $ref: '#/definitions/ListenerConfig' metadata: @@ -674,7 +706,7 @@ paths: - '[]' summary: Get current config. tags: - - ConfigManagement + - Config post: operationId: saveConfigRequest parameters: @@ -691,7 +723,7 @@ paths: - '[]' summary: Save current config to file (gost.yaml or gost.json). tags: - - ConfigManagement + - Config /config/admissions: post: operationId: createAdmissionRequest @@ -709,7 +741,7 @@ paths: - '[]' summary: Create a new admission, the name of admission must be unique in admission list. tags: - - ConfigManagement + - Admission /config/admissions/{admission}: delete: operationId: deleteAdmissionRequest @@ -727,7 +759,7 @@ paths: - '[]' summary: Delete admission by name. tags: - - ConfigManagement + - Admission put: operationId: updateAdmissionRequest parameters: @@ -749,7 +781,7 @@ paths: - '[]' summary: Update admission by name, the admission must already exist. tags: - - ConfigManagement + - Admission /config/authers: post: operationId: createAutherRequest @@ -767,7 +799,7 @@ paths: - '[]' summary: Create a new auther, the name of the auther must be unique in auther list. tags: - - ConfigManagement + - Auther /config/authers/{auther}: delete: operationId: deleteAutherRequest @@ -785,7 +817,7 @@ paths: - '[]' summary: Delete auther by name. tags: - - ConfigManagement + - Auther put: operationId: updateAutherRequest parameters: @@ -807,7 +839,7 @@ paths: - '[]' summary: Update auther by name, the auther must already exist. tags: - - ConfigManagement + - Auther /config/bypasses: post: operationId: createBypassRequest @@ -825,7 +857,7 @@ paths: - '[]' summary: Create a new bypass, the name of bypass must be unique in bypass list. tags: - - ConfigManagement + - Bypass /config/bypasses/{bypass}: delete: operationId: deleteBypassRequest @@ -843,7 +875,7 @@ paths: - '[]' summary: Delete bypass by name. tags: - - ConfigManagement + - Bypass put: operationId: updateBypassRequest parameters: @@ -865,7 +897,7 @@ paths: - '[]' summary: Update bypass by name, the bypass must already exist. tags: - - ConfigManagement + - Bypass /config/chains: post: operationId: createChainRequest @@ -883,7 +915,7 @@ paths: - '[]' summary: Create a new chain, the name of chain must be unique in chain list. tags: - - ConfigManagement + - Chain /config/chains/{chain}: delete: operationId: deleteChainRequest @@ -901,7 +933,7 @@ paths: - '[]' summary: Delete chain by name. tags: - - ConfigManagement + - Chain put: operationId: updateChainRequest parameters: @@ -923,7 +955,7 @@ paths: - '[]' summary: Update chain by name, the chain must already exist. tags: - - ConfigManagement + - Chain /config/hosts: post: operationId: createHostsRequest @@ -941,7 +973,7 @@ paths: - '[]' summary: Create a new hosts, the name of the hosts must be unique in hosts list. tags: - - ConfigManagement + - Hosts /config/hosts/{hosts}: delete: operationId: deleteHostsRequest @@ -959,7 +991,7 @@ paths: - '[]' summary: Delete hosts by name. tags: - - ConfigManagement + - Hosts put: operationId: updateHostsRequest parameters: @@ -981,7 +1013,64 @@ paths: - '[]' summary: Update hosts by name, the hosts must already exist. tags: - - ConfigManagement + - Hosts + /config/limiters: + post: + operationId: createLimiterRequest + parameters: + - in: body + name: data + schema: + $ref: '#/definitions/LimiterConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/createLimiterResponse' + security: + - basicAuth: + - '[]' + summary: Create a new limiter, the name of limiter must be unique in limiter list. + tags: + - Limiter + /config/limiters/{limiter}: + delete: + operationId: deleteLimiterRequest + parameters: + - in: path + name: Limiter + required: true + type: string + responses: + "200": + $ref: '#/responses/deleteLimiterResponse' + security: + - basicAuth: + - '[]' + summary: Delete limiter by name. + tags: + - Limiter + put: + operationId: updateLimiterRequest + parameters: + - in: path + name: limiter + required: true + type: string + x-go-name: Limiter + - in: body + name: data + schema: + $ref: '#/definitions/LimiterConfig' + x-go-name: Data + responses: + "200": + $ref: '#/responses/updateLimiterResponse' + security: + - basicAuth: + - '[]' + summary: Update limiter by name, the limiter must already exist. + tags: + - Limiter /config/resolvers: post: operationId: createResolverRequest @@ -999,7 +1088,7 @@ paths: - '[]' summary: Create a new resolver, the name of the resolver must be unique in resolver list. tags: - - ConfigManagement + - Resolver /config/resolvers/{resolver}: delete: operationId: deleteResolverRequest @@ -1017,7 +1106,7 @@ paths: - '[]' summary: Delete resolver by name. tags: - - ConfigManagement + - Resolver put: operationId: updateResolverRequest parameters: @@ -1039,7 +1128,7 @@ paths: - '[]' summary: Update resolver by name, the resolver must already exist. tags: - - ConfigManagement + - Resolver /config/services: post: operationId: createServiceRequest @@ -1057,7 +1146,7 @@ paths: - '[]' summary: Create a new service, the name of the service must be unique in service list. tags: - - ConfigManagement + - Service /config/services/{service}: delete: operationId: deleteServiceRequest @@ -1075,7 +1164,7 @@ paths: - '[]' summary: Delete service by name. tags: - - ConfigManagement + - Service put: operationId: updateServiceRequest parameters: @@ -1097,7 +1186,7 @@ paths: - '[]' summary: Update service by name, the service must already exist. tags: - - ConfigManagement + - Service produces: - application/json responses: @@ -1131,6 +1220,12 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + createLimiterResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' createResolverResponse: description: successful operation. headers: @@ -1173,6 +1268,12 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + deleteLimiterResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' deleteResolverResponse: description: successful operation. headers: @@ -1227,6 +1328,12 @@ responses: Data: {} schema: $ref: '#/definitions/Response' + updateLimiterResponse: + description: successful operation. + headers: + Data: {} + schema: + $ref: '#/definitions/Response' updateResolverResponse: description: successful operation. headers: diff --git a/go.mod b/go.mod index 72deaaf..0d8bc4e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/go-gost/x go 1.18 require ( - github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.7.7 @@ -33,7 +33,7 @@ require ( golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 golang.org/x/net v0.0.0-20220812174116-3211cb980234 golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 - golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478 google.golang.org/grpc v1.49.0 google.golang.org/protobuf v1.27.1 diff --git a/go.sum b/go.sum index 710aeda..d943fcf 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= @@ -593,6 +595,8 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=