Introduce Lifecycle for every endpoint and manage listeners in the renamed Orchestrator

- merge packages to get a more concise layout because plugins are no more and therefore there's not a lot to be exported
- fix test logger
- rework config parsing to be easier and more transparent
- remove unnecessary APIs because dynamic endpoint handling is rather a won't implement
This commit is contained in:
Peter 2021-02-10 20:26:45 +00:00
parent dd4b191abb
commit d70ba748f5
79 changed files with 1839 additions and 2036 deletions

3
.gitignore vendored
View file

@ -22,4 +22,5 @@
.vscode/ .vscode/
dist/ dist/
out/ out/
.task/ .task/
public/

View file

@ -1,4 +1,4 @@
image: registry.gitlab.com/inetmock/ci-image image: registry.gitlab.com/inetmock/ci-image/go
stages: stages:
- test - test
@ -14,6 +14,13 @@ test:
junit: out/report.xml junit: out/report.xml
cobertura: out/coverage.xml cobertura: out/coverage.xml
integration-test:
stage: test
services:
- docker:dind
script:
- task integration-test
lint: lint:
stage: test stage: test
script: script:
@ -42,3 +49,16 @@ release:
script: script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- goreleaser release --rm-dist - goreleaser release --rm-dist
docs:
stage: release
image: registry.gitlab.com/inetmock/ci-image/mdbook
only:
refs:
- master
- tags
script:
- mdbook build -d ./../public ./docs
artifacts:
paths:
- public

View file

@ -1,13 +1,9 @@
# INetMock # INetMock
![Go](https://gitlab.com/inetmock/inetmock/workflows/Go/badge.svg)
[![pipeline status](https://gitlab.com/inetmock/inetmock/badges/master/pipeline.svg)](https://gitlab.com/inetmock/inetmock/-/commits/master)
[![coverage report](https://gitlab.com/inetmock/inetmock/badges/master/coverage.svg)](https://gitlab.com/inetmock/inetmock/-/commits/master)
[![Go Report Card](https://goreportcard.com/badge/gitlab.com/inetmock/inetmock)](https://goreportcard.com/report/gitlab.com/inetmock/inetmock) [![Go Report Card](https://goreportcard.com/badge/gitlab.com/inetmock/inetmock)](https://goreportcard.com/report/gitlab.com/inetmock/inetmock)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=alert_status)](https://sonarcloud.io/dashboard?id=baez90_inetmock)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=ncloc)](https://sonarcloud.io/dashboard?id=baez90_inetmock)
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=baez90_inetmock)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=security_rating)](https://sonarcloud.io/dashboard?id=baez90_inetmock)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=bugs)](https://sonarcloud.io/dashboard?id=baez90_inetmock)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=baez90_inetmock&metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=baez90_inetmock)
INetMock is kind of a fork of [INetSim](https://www.inetsim.org/). INetMock is kind of a fork of [INetSim](https://www.inetsim.org/).
"Kind of" in terms that both applications overlap in their functionality to serve as "fake internet" routers. "Kind of" in terms that both applications overlap in their functionality to serve as "fake internet" routers.
@ -17,18 +13,15 @@ HTTPS, DNS, DNS-over-TLS (DoT) requests and to act as an HTTP proxy. The most no
is that it issues proper TLS certificates on the fly signed by a CA certificate that can be deployed to client systems is that it issues proper TLS certificates on the fly signed by a CA certificate that can be deployed to client systems
to achieve "proper" TLS encryption - as long as the client does not use certificate pinning or something similar. to achieve "proper" TLS encryption - as long as the client does not use certificate pinning or something similar.
A second advantage is that INetMock is a complete rewrite in Go, based on a plugin system that allows dynamic A second advantage is that INetMock is a complete rewrite in Go.
configuration while it has a way smaller memory footprint and far better startup and shutdown times. It also does not It has a way smaller memory footprint and far better startup and shutdown times.
enforce `root` privileges as it is also possible to run the application with the required capabilities to open ports It also does not enforce `root` privileges as it is also possible to run the application with the required capabilities to open ports e.g. with SystemD (a sample unit file can be found in the `deploy/` directory).
e.g. with SystemD (a sample unit file can be found in the `deploy/` directory).
_This project is still heavy work-in-progress. There may be breaking changes at any time. There's no guarantee for _This project is still heavy work-in-progress. There may be breaking changes at any time. There's no guarantee for anything except no kittens will be harmed!_
anything except no kittens will be harmed!_
## Docs ## Docs
Docs are available either in the [`docs/`](./docs/) directory or as rendered markdown book at Docs are available either in the [`docs/`](./docs/) directory or as rendered markdown book at the [GitHub pages](https://baez90.github.io/inetmock/).
the [GitHub pages](https://baez90.github.io/inetmock/).
## Contribution/feature requests ## Contribution/feature requests

View file

@ -6,6 +6,8 @@ vars:
IMCTL_PKG: gitlab.com/inetmock/inetmock/cmd/imctl IMCTL_PKG: gitlab.com/inetmock/inetmock/cmd/imctl
PROTO_FILES: PROTO_FILES:
sh: find ./api/ -type f -name "*.proto" -printf "%p " sh: find ./api/ -type f -name "*.proto" -printf "%p "
BENCHMARKS:
sh: find . -type f -name "*_bench_test.go"
env: env:
GOOS: linux GOOS: linux
@ -53,6 +55,15 @@ tasks:
- gocover-cobertura < {{ .OUT_DIR }}/cov.out > {{ .OUT_DIR }}/coverage.xml - gocover-cobertura < {{ .OUT_DIR }}/cov.out > {{ .OUT_DIR }}/coverage.xml
- rm -f {{ .OUT_DIR }}/cov-raw.out - rm -f {{ .OUT_DIR }}/cov-raw.out
integration-test:
deps:
- generate
cmds:
- |
{{ range .BENCHMARKS | splitLines -}}
go test -bench=. {{ . }}
{{ end }}
cli-cover-report: cli-cover-report:
deps: deps:
- test - test
@ -98,4 +109,8 @@ tasks:
deps: deps:
- test - test
cmds: cmds:
- goreleaser release - goreleaser release
docs:
cmds:
- mdbook build -d ./../public ./docs

View file

@ -1,28 +0,0 @@
syntax = "proto3";
option go_package = "gitlab.com/inetmock/inetmock/internal/rpc";
option java_multiple_files = true;
option java_package = "com.github.baez90.inetmock.rpc";
option java_outer_classname = "EndpointsProto";
package inetmock.rpc;
message GetEndpointsRequest {
}
message GetEndpointsResponse {
repeated Endpoint endpoints = 1;
}
message Endpoint {
string id = 1;
string name = 2;
string handler = 3;
string listenAddress = 4;
int32 port = 5;
}
service Endpoints {
rpc GetEndpoints (GetEndpointsRequest) returns (GetEndpointsResponse) {
}
}

View file

@ -1,20 +0,0 @@
syntax = "proto3";
option go_package = "gitlab.com/inetmock/inetmock/internal/rpc";
option java_multiple_files = true;
option java_package = "com.github.baez90.inetmock.rpc";
option java_outer_classname = "HandlersProto";
package inetmock.rpc;
service Handlers {
rpc GetHandlers (GetHandlersRequest) returns (GetHandlersResponse) {
}
}
message GetHandlersRequest {
}
message GetHandlersResponse {
repeated string handlers = 1;
}

View file

@ -1,67 +0,0 @@
package main
import (
"context"
"fmt"
"os"
"github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/format"
"gitlab.com/inetmock/inetmock/internal/rpc"
)
var (
getEndpoints = &cobra.Command{
Use: "get",
Short: "Get all running endpoints",
RunE: runGetEndpoints,
}
endpointsCmd = &cobra.Command{
Use: "endpoints",
Short: "endpoints is the entrypoint to all kind of commands to interact with endpoints",
Aliases: []string{"ep", "endpoint"},
}
)
type printableEndpoint struct {
ID string
Name string
Handler string
ListenAddress string
Port int
}
func fromEndpoint(ep *rpc.Endpoint) *printableEndpoint {
return &printableEndpoint{
ID: ep.Id,
Name: ep.Name,
Handler: ep.Handler,
ListenAddress: ep.ListenAddress,
Port: int(ep.Port),
}
}
func fromEndpoints(eps []*rpc.Endpoint) (out []*printableEndpoint) {
for idx := range eps {
out = append(out, fromEndpoint(eps[idx]))
}
return
}
func runGetEndpoints(_ *cobra.Command, _ []string) (err error) {
endpointsClient := rpc.NewEndpointsClient(conn)
ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
defer cancel()
var endpointsResp *rpc.GetEndpointsResponse
if endpointsResp, err = endpointsClient.GetEndpoints(ctx, &rpc.GetEndpointsRequest{}); err != nil {
fmt.Printf("Failed to get the endpoints: %v", err)
os.Exit(11)
}
writer := format.Writer(outputFormat, os.Stdout)
if err = writer.Write(fromEndpoints(endpointsResp.Endpoints)); err != nil {
fmt.Printf("Error occurred during writing response values: %v\n", err)
}
return
}

View file

@ -1,57 +0,0 @@
package main
import (
"context"
"fmt"
"os"
"github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/format"
"gitlab.com/inetmock/inetmock/internal/rpc"
)
var (
getHandlersCmd = &cobra.Command{
Use: "get",
Short: "Get all registered handlers",
Run: runGetHandlers,
}
handlerCmd = &cobra.Command{
Use: "handlers",
Short: "handlers is the entrypoint to all kind of commands to interact with handlers",
Aliases: []string{"handler"},
}
)
type printableHandler struct {
Handler string
}
func fromHandlers(hs []string) (handlers []*printableHandler) {
for idx := range hs {
handlers = append(handlers, &printableHandler{
Handler: hs[idx],
})
}
return
}
func runGetHandlers(_ *cobra.Command, _ []string) {
handlersClient := rpc.NewHandlersClient(conn)
ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
defer cancel()
var err error
var handlersResp *rpc.GetHandlersResponse
if handlersResp, err = handlersClient.GetHandlers(ctx, &rpc.GetHandlersRequest{}); err != nil {
fmt.Printf("Failed to get the endpoints: %v", err)
os.Exit(11)
}
writer := format.Writer(outputFormat, os.Stdout)
if err = writer.Write(fromHandlers(handlersResp.Handlers)); err != nil {
fmt.Printf("Error occurred during writing response values: %v\n", err)
}
}

View file

@ -22,12 +22,10 @@ var (
) )
func main() { func main() {
endpointsCmd.AddCommand(getEndpoints)
handlerCmd.AddCommand(getHandlersCmd)
healthCmd.AddCommand(generalHealthCmd, containerHealthCmd) healthCmd.AddCommand(generalHealthCmd, containerHealthCmd)
cliApp = app.NewApp("imctl", "IMCTL is the CLI app to interact with an INetMock server"). cliApp = app.NewApp("imctl", "IMCTL is the CLI app to interact with an INetMock server").
WithCommands(endpointsCmd, handlerCmd, healthCmd, auditCmd). WithCommands(healthCmd, auditCmd).
WithInitTasks(func(_ *cobra.Command, _ []string) (err error) { WithInitTasks(func(_ *cobra.Command, _ []string) (err error) {
return initGRPCConnection() return initGRPCConnection()
}). }).

View file

@ -7,7 +7,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/pkg/cert" "gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/config"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -64,11 +63,11 @@ func runGenerateCA(_ *cobra.Command, _ []string) {
zap.String(generateCACertOutPath, certOutPath), zap.String(generateCACertOutPath, certOutPath),
) )
generator := cert.NewDefaultGenerator(config.CertOptions{ generator := cert.NewDefaultGenerator(cert.CertOptions{
CertCachePath: certOutPath, CertCachePath: certOutPath,
Curve: config.CurveType(curveName), Curve: cert.CurveType(curveName),
Validity: config.ValidityByPurpose{ Validity: cert.ValidityByPurpose{
CA: config.ValidityDuration{ CA: cert.ValidityDuration{
NotAfterRelative: notAfter, NotAfterRelative: notAfter,
NotBeforeRelative: notBefore, NotBeforeRelative: notBefore,
}, },

View file

@ -1,11 +1,8 @@
package main package main
import ( import (
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/rpc" "gitlab.com/inetmock/inetmock/internal/rpc"
"gitlab.com/inetmock/inetmock/pkg/config"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -14,41 +11,48 @@ var (
Use: "serve", Use: "serve",
Short: "Starts the INetMock server", Short: "Starts the INetMock server",
Long: ``, Long: ``,
Run: startINetMock, RunE: startINetMock,
} }
) )
func startINetMock(_ *cobra.Command, _ []string) { func startINetMock(_ *cobra.Command, _ []string) (err error) {
rpcAPI := rpc.NewINetMockAPI(serverApp) rpcAPI := rpc.NewINetMockAPI(serverApp)
logger := serverApp.Logger().Named("inetmock").With(zap.String("command", "serve")) logger := serverApp.Logger()
for endpointName, endpointHandler := range serverApp.Config().EndpointConfigs() { cfg := serverApp.Config()
handlerSubConfig := serverApp.Config().Viper().Sub(strings.Join([]string{config.EndpointsKey, endpointName, config.OptionsKey}, ".")) endpointOrchestrator := serverApp.EndpointManager()
endpointHandler.Options = handlerSubConfig
if err := serverApp.EndpointManager().CreateEndpoint(endpointName, endpointHandler); err != nil { for name, spec := range cfg.ListenerSpecs() {
logger.Warn( if spec.Name == "" {
"error occurred while creating endpoint", spec.Name = name
zap.String("endpointName", endpointName), }
zap.String("handlerName", endpointHandler.Handler), if err = endpointOrchestrator.RegisterListener(spec); err != nil {
zap.Error(err), logger.Error("Failed to register listener", zap.Error(err))
) return
} }
} }
serverApp.EndpointManager().StartEndpoints() errChan := serverApp.EndpointManager().StartEndpoints()
if err := rpcAPI.StartServer(); err != nil { if err = rpcAPI.StartServer(); err != nil {
serverApp.Shutdown()
logger.Error( logger.Error(
"failed to start gRPC API", "failed to start gRPC API",
zap.Error(err), zap.Error(err),
) )
} }
<-serverApp.Context().Done() loop:
for {
select {
case err := <-errChan:
logger.Error("got error from endpoint", zap.Error(err))
case <-serverApp.Context().Done():
break loop
}
}
logger.Info( logger.Info("App context canceled - shutting down")
"App context canceled - shutting down",
)
rpcAPI.StopServer() rpcAPI.StopServer()
serverApp.EndpointManager().ShutdownEndpoints() return
} }

View file

@ -41,6 +41,19 @@ x-response-rules: &httpResponseRules
matcher: Path matcher: Path
response: /var/lib/inetmock/fakeFiles/default.html response: /var/lib/inetmock/fakeFiles/default.html
x-http-handlers: &httpHandlers
endpoints:
plainHttp:
handler: http_mock
tls: false
options:
<<: *httpResponseRules
https:
handler: http_mock
tls: true
options:
<<: *httpResponseRules
api: api:
listen: unix:///var/run/inetmock/inetmock.sock listen: unix:///var/run/inetmock/inetmock.sock
@ -60,61 +73,94 @@ tls:
privateKeyPath: /var/lib/inetmock/ca/ca.key privateKeyPath: /var/lib/inetmock/ca/ca.key
certCachePath: /var/lib/inetmock/certs certCachePath: /var/lib/inetmock/certs
endpoints: listeners:
plainHttp: udp_53:
handler: http_mock name: ''
listenAddress: 0.0.0.0 protocol: udp
ports: listenAddress: ''
- 80 port: 53
- 8080 endpoints:
options: plainDns:
<<: *httpResponseRules handler: dns_mock
https: options:
handler: http_mock rules:
listenAddress: 0.0.0.0 - pattern: ".*\\.google\\.com"
ports: response: 1.1.1.1
- 443 - pattern: ".*\\.reddit\\.com"
- 8443 response: 2.2.2.2
options: fallback:
tls: true strategy: incremental
<<: *httpResponseRules args:
proxy: startIP: 10.0.10.0
handler: http_proxy tcp_80:
listenAddress: 0.0.0.0 name: ''
ports: protocol: tcp
- 3128 listenAddress: ''
options: port: 80
target: <<: *httpHandlers
ipAddress: 127.0.0.1 tcp_443:
port: 80 name: ''
plainDns: protocol: tcp
handler: dns_mock listenAddress: ''
listenAddress: 0.0.0.0 port: 443
ports: <<: *httpHandlers
- 53 tcp_853:
options: name: ''
rules: protocol: tcp
- pattern: ".*\\.google\\.com" listenAddress: ''
response: 1.1.1.1 port: 853
- pattern: ".*\\.reddit\\.com" endpoints:
response: 2.2.2.2 DoT:
fallback: handler: dns_mock
strategy: incremental tls: true
args: options:
startIP: 10.0.10.0 rules:
dnsOverTlsDowngrade: - pattern: ".*\\.google\\.com"
handler: tls_interceptor response: 1.1.1.1
listenAddress: 0.0.0.0 - pattern: ".*\\.reddit\\.com"
ports: response: 2.2.2.2
- 853 fallback:
options: strategy: incremental
target: args:
ipAddress: 127.0.0.1 startIP: 10.0.10.0
port: 53 tcp_3128:
metrics: name: ''
handler: metrics_exporter protocol: tcp
listenAddress: 0.0.0.0 listenAddress: ''
ports: port: 3128
- 9110 endpoints:
options: proxyPlain:
route: /metrics handler: http_proxy
options:
target:
ipAddress: 127.0.0.1
port: 80
proxyTls:
handler: http_proxy
tls: true
options:
target:
ipAddress: 127.0.0.1
port: 443
tcp_8080:
name: ''
protocol: tcp
listenAddress: ''
port: 8080
<<: *httpHandlers
tcp_8443:
name: ''
protocol: tcp
listenAddress: ''
port: 8443
<<: *httpHandlers
tcp_9110:
name: ''
protocol: tcp
listenAddress: ''
port: 9110
endpoints:
metrics:
handler: metrics_exporter
options:
route: /metrics

View file

@ -41,6 +41,19 @@ x-response-rules: &httpResponseRules
matcher: Path matcher: Path
response: ./assets/fakeFiles/default.html response: ./assets/fakeFiles/default.html
x-http-handlers: &httpHandlers
endpoints:
plainHttp:
handler: http_mock
tls: false
options:
<<: *httpResponseRules
https:
handler: http_mock
tls: true
options:
<<: *httpResponseRules
api: api:
listen: unix:///var/run/inetmock.sock listen: unix:///var/run/inetmock.sock
@ -60,61 +73,94 @@ tls:
privateKeyPath: ./assets/demoCA/ca.key privateKeyPath: ./assets/demoCA/ca.key
certCachePath: /tmp/inetmock/ certCachePath: /tmp/inetmock/
endpoints: listeners:
plainHttp: udp_53:
handler: http_mock name: ''
listenAddress: 0.0.0.0 protocol: udp
ports: listenAddress: ''
- 80 port: 1053
- 8080 endpoints:
options: plainDns:
<<: *httpResponseRules handler: dns_mock
https: options:
handler: http_mock rules:
listenAddress: 0.0.0.0 - pattern: ".*\\.google\\.com"
ports: response: 1.1.1.1
- 443 - pattern: ".*\\.reddit\\.com"
- 8443 response: 2.2.2.2
options: fallback:
tls: true strategy: incremental
<<: *httpResponseRules args:
proxy: startIP: 10.0.10.0
handler: http_proxy tcp_80:
listenAddress: 0.0.0.0 name: ''
ports: protocol: tcp
- 3128 listenAddress: ''
options: port: 80
target: <<: *httpHandlers
ipAddress: 127.0.0.1 tcp_443:
port: 80 name: ''
plainDns: protocol: tcp
handler: dns_mock listenAddress: ''
listenAddress: 0.0.0.0 port: 443
ports: <<: *httpHandlers
- 53 tcp_853:
options: name: ''
rules: protocol: tcp
- pattern: ".*\\.google\\.com" listenAddress: ''
response: 1.1.1.1 port: 853
- pattern: ".*\\.reddit\\.com" endpoints:
response: 2.2.2.2 DoT:
fallback: handler: dns_mock
strategy: incremental tls: true
args: options:
startIP: 10.0.10.0 rules:
dnsOverTlsDowngrade: - pattern: ".*\\.google\\.com"
handler: tls_interceptor response: 1.1.1.1
listenAddress: 0.0.0.0 - pattern: ".*\\.reddit\\.com"
ports: response: 2.2.2.2
- 853 fallback:
options: strategy: incremental
target: args:
ipAddress: 127.0.0.1 startIP: 10.0.10.0
port: 53 tcp_3128:
metrics: name: ''
handler: metrics_exporter protocol: tcp
listenAddress: 0.0.0.0 listenAddress: ''
ports: port: 3128
- 9110 endpoints:
options: proxyPlain:
route: /metrics handler: http_proxy
options:
target:
ipAddress: 127.0.0.1
port: 80
proxyTls:
handler: http_proxy
tls: true
options:
target:
ipAddress: 127.0.0.1
port: 443
tcp_8080:
name: ''
protocol: tcp
listenAddress: ''
port: 8080
<<: *httpHandlers
tcp_8443:
name: ''
protocol: tcp
listenAddress: ''
port: 8443
<<: *httpHandlers
tcp_9110:
name: ''
protocol: tcp
listenAddress: ''
port: 9110
endpoints:
metrics:
handler: metrics_exporter
options:
route: /metrics

12
go.mod
View file

@ -4,16 +4,20 @@ go 1.15
require ( require (
github.com/bwmarrin/snowflake v0.3.0 github.com/bwmarrin/snowflake v0.3.0
github.com/docker/go-connections v0.4.0
github.com/golang/mock v1.4.4 github.com/golang/mock v1.4.4
github.com/golang/protobuf v1.4.3 github.com/golang/protobuf v1.4.3
github.com/google/uuid v1.2.0 github.com/google/uuid v1.2.0
github.com/imdario/mergo v0.3.11 github.com/imdario/mergo v0.3.11
github.com/miekg/dns v1.1.35 github.com/jinzhu/copier v0.2.4
github.com/olekukonko/tablewriter v0.0.4 github.com/miekg/dns v1.1.38
github.com/mitchellh/mapstructure v1.4.1
github.com/olekukonko/tablewriter v0.0.5
github.com/prometheus/client_golang v1.9.0 github.com/prometheus/client_golang v1.9.0
github.com/spf13/cobra v1.1.1 github.com/soheilhy/cmux v0.1.4
github.com/spf13/pflag v1.0.5 github.com/spf13/cobra v1.1.2
github.com/spf13/viper v1.7.1 github.com/spf13/viper v1.7.1
github.com/testcontainers/testcontainers-go v0.9.0
go.uber.org/multierr v1.6.0 go.uber.org/multierr v1.6.0
go.uber.org/zap v1.16.0 go.uber.org/zap v1.16.0
google.golang.org/grpc v1.35.0 google.golang.org/grpc v1.35.0

168
go.sum
View file

@ -11,10 +11,16 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
@ -28,7 +34,6 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
@ -45,6 +50,7 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
@ -53,12 +59,15 @@ github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY=
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@ -74,20 +83,28 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible h1:SiUATuP//KecDjpOK2tvZJgeScYAklvyjfK8JZlU6fo=
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 h1:Am81SElhR3XCQBunTisljzNkNese2T1FiV8jP79+dqg= github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e h1:/cwV7t2xezilMljIftb7WlFtzGANRCnoOhPjtl2ifcs=
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/elazarl/goproxy/ext v0.0.0-20201021153353-00ad82a08272 h1:xOHQWEGsftkjjFV2KIrC9vOz+iOinvZ7H6EAjSznqUk= github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e h1:CQn2/8fi3kmpT9BTiHEELgdxAOQNVZc9GoPA4qnQzrs=
github.com/elazarl/goproxy/ext v0.0.0-20201021153353-00ad82a08272/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -95,9 +112,9 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@ -105,15 +122,25 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -123,6 +150,7 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -159,9 +187,9 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@ -192,6 +220,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
@ -199,12 +228,15 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jinzhu/copier v0.2.4 h1:dT3tI+8GzU8DjJFCj9mLYtjfRtUmK7edauduQdcZCpI=
github.com/jinzhu/copier v0.2.4/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@ -214,38 +246,33 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -255,14 +282,14 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.0 h1:7ks8ZkOP5/ujthUsT07rNv+nkLXCQWKNHuwzOAesEks=
github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
@ -276,12 +303,22 @@ github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtb
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -295,17 +332,15 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@ -347,14 +382,8 @@ github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFB
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.3.0 h1:Uehi/mxLK0eiUc0H0++5tpMGTexB8wZ598MIgU8VpDM=
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -365,36 +394,29 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg=
github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= github.com/spf13/cobra v1.1.2 h1:frHO75w/dH7kEc+e2KYZZKY4+PLrp39OqI77oB8m0KQ=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.2/go.mod h1:ZjwqWkCg0LnXvLRIfTLdB4Y/MCO3gMHHJ2KFxQZy4xE=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
@ -409,15 +431,19 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/testcontainers/testcontainers-go v0.9.0 h1:ZyftCfROjGrKlxk3MOUn2DAzWrUtzY/mj17iAkdUIvI=
github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
@ -425,6 +451,7 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -452,13 +479,10 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -493,7 +517,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
@ -503,11 +526,10 @@ golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -536,37 +558,26 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 h1:vRgIt+nup/B/BwIS0g2oC0haq0iqbV3ZA+u6+0TlNCo=
golang.org/x/sys v0.0.0-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-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-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -590,6 +601,7 @@ golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktyp
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -619,8 +631,8 @@ google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgX
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506 h1:uLBY0yHDCj2PMQ98KWDSIDFwn9zK2zh+tgWtbvPPBjI= google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28=
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@ -633,10 +645,11 @@ google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc/examples v0.0.0-20210210171350-ce29c77c5fa1 h1:dtHfS+TF/MZg8X+DaxrDGiq+5z8OrIJJc1yX+8cQ0cU=
google.golang.org/grpc/examples v0.0.0-20210210171350-ce29c77c5fa1/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -659,13 +672,13 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
gopkg.in/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153 h1:i2sumy6EgvN2dbX7HPhoDc7hLyoym3OYdU5HlvUUrpE= gopkg.in/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153 h1:i2sumy6EgvN2dbX7HPhoDc7hLyoym3OYdU5HlvUUrpE=
gopkg.in/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153/go.mod h1:xzjpkyedLMz3EXUTBbkRuuGPsxfsBX3Sy7J6kC9Gvoc= gopkg.in/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153/go.mod h1:xzjpkyedLMz3EXUTBbkRuuGPsxfsBX3Sy7J6kC9Gvoc=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
@ -680,10 +693,11 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v0.0.0-20181223230014-1083505acf35 h1:zpdCK+REwbk+rqjJmHhiCN6iBIigrZ39glqSF0P3KF0=
gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View file

@ -10,15 +10,12 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/endpoint" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/pkg/audit" "gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/audit/sink" "gitlab.com/inetmock/inetmock/pkg/audit/sink"
"gitlab.com/inetmock/inetmock/pkg/cert" "gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/health" "gitlab.com/inetmock/inetmock/pkg/health"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/path" "gitlab.com/inetmock/inetmock/pkg/path"
"go.uber.org/multierr"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -41,12 +38,12 @@ const (
) )
type App interface { type App interface {
api.PluginContext
EventStream() audit.EventStream EventStream() audit.EventStream
Config() config.Config Config() Config
Checker() health.Checker Checker() health.Checker
EndpointManager() endpoint.EndpointManager Logger() logging.Logger
HandlerRegistry() api.HandlerRegistry EndpointManager() endpoint.Orchestrator
HandlerRegistry() endpoint.HandlerRegistry
Context() context.Context Context() context.Context
RootCommand() *cobra.Command RootCommand() *cobra.Command
MustRun() MustRun()
@ -58,7 +55,7 @@ type App interface {
// WithHandlerRegistry builds up the handler registry // WithHandlerRegistry builds up the handler registry
// requires nothing // requires nothing
WithHandlerRegistry(registrations ...api.Registration) App WithHandlerRegistry(registrations ...endpoint.Registration) App
// WithHealthChecker adds the health checker mechanism // WithHealthChecker adds the health checker mechanism
// requires nothing // requires nothing
@ -115,12 +112,12 @@ func (a *app) Logger() logging.Logger {
return val.(logging.Logger) return val.(logging.Logger)
} }
func (a *app) Config() config.Config { func (a *app) Config() Config {
val := a.ctx.Value(configKey) val := a.ctx.Value(configKey)
if val == nil { if val == nil {
return nil return nil
} }
return val.(config.Config) return val.(Config)
} }
func (a *app) CertStore() cert.Store { func (a *app) CertStore() cert.Store {
@ -139,12 +136,12 @@ func (a *app) Checker() health.Checker {
return val.(health.Checker) return val.(health.Checker)
} }
func (a *app) EndpointManager() endpoint.EndpointManager { func (a *app) EndpointManager() endpoint.Orchestrator {
val := a.ctx.Value(endpointManagerKey) val := a.ctx.Value(endpointManagerKey)
if val == nil { if val == nil {
return nil return nil
} }
return val.(endpoint.EndpointManager) return val.(endpoint.Orchestrator)
} }
func (a *app) Audit() audit.Emitter { func (a *app) Audit() audit.Emitter {
@ -163,12 +160,12 @@ func (a *app) EventStream() audit.EventStream {
return val.(audit.EventStream) return val.(audit.EventStream)
} }
func (a *app) HandlerRegistry() api.HandlerRegistry { func (a *app) HandlerRegistry() endpoint.HandlerRegistry {
val := a.ctx.Value(handlerRegistryKey) val := a.ctx.Value(handlerRegistryKey)
if val == nil { if val == nil {
return nil return nil
} }
return val.(api.HandlerRegistry) return val.(endpoint.HandlerRegistry)
} }
func (a *app) Context() context.Context { func (a *app) Context() context.Context {
@ -192,8 +189,8 @@ func (a *app) WithCommands(cmds ...*cobra.Command) App {
// WithHandlerRegistry builds up the handler registry // WithHandlerRegistry builds up the handler registry
// requires nothing // requires nothing
func (a *app) WithHandlerRegistry(registrations ...api.Registration) App { func (a *app) WithHandlerRegistry(registrations ...endpoint.Registration) App {
registry := api.NewHandlerRegistry() registry := endpoint.NewHandlerRegistry()
for _, registration := range registrations { for _, registration := range registrations {
if err := registration(registry); err != nil { if err := registration(registry); err != nil {
@ -242,11 +239,12 @@ func (a *app) WithLogger() App {
// requires WithHandlerRegistry, WithHealthChecker and WithLogger // requires WithHandlerRegistry, WithHealthChecker and WithLogger
func (a *app) WithEndpointManager() App { func (a *app) WithEndpointManager() App {
a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) { a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) {
epMgr := endpoint.NewEndpointManager( epMgr := endpoint.NewOrchestrator(
a.Context(),
a.CertStore(),
a.HandlerRegistry(), a.HandlerRegistry(),
a.Logger().Named("EndpointManager"), a.Audit(),
a.Checker(), a.Logger().Named("Orchestrator"),
a,
) )
a.ctx = context.WithValue(a.ctx, endpointManagerKey, epMgr) a.ctx = context.WithValue(a.ctx, endpointManagerKey, epMgr)
@ -261,7 +259,7 @@ func (a *app) WithCertStore() App {
a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, args []string) (err error) { a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, args []string) (err error) {
var certStore cert.Store var certStore cert.Store
if certStore, err = cert.NewDefaultStore( if certStore, err = cert.NewDefaultStore(
a.Config(), a.Config().TLSConfig(),
a.Logger().Named("CertStore"), a.Logger().Named("CertStore"),
); err != nil { ); err != nil {
return return
@ -310,7 +308,7 @@ func (a *app) WithEventStream() App {
// requires nothing // requires nothing
func (a *app) WithConfig() App { func (a *app) WithConfig() App {
a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, _ []string) (err error) { a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, _ []string) (err error) {
cfg := config.CreateConfig(cmd.Flags()) cfg := CreateConfig()
if err = cfg.ReadConfig(configFilePath); err != nil { if err = cfg.ReadConfig(configFilePath); err != nil {
return return
} }
@ -343,7 +341,9 @@ func NewApp(name, short string) App {
a.rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) { a.rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) {
for _, initTask := range a.lateInitTasks { for _, initTask := range a.lateInitTasks {
err = multierr.Append(err, initTask(cmd, args)) if err = initTask(cmd, args); err != nil {
return
}
} }
return return
} }

View file

@ -1,20 +1,17 @@
package config package app
import ( import (
"strings" "strings"
"github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/path" "gitlab.com/inetmock/inetmock/pkg/path"
"go.uber.org/zap"
) )
func CreateConfig(flags *pflag.FlagSet) Config { func CreateConfig() Config {
logger, _ := logging.CreateLogger()
configInstance := &config{ configInstance := &config{
logger: logger.Named("Config"), cfg: viper.New(),
cfg: viper.New(),
} }
configInstance.cfg.SetConfigName("config") configInstance.cfg.SetConfigName("config")
@ -23,7 +20,6 @@ func CreateConfig(flags *pflag.FlagSet) Config {
configInstance.cfg.AddConfigPath("$HOME/.inetmock") configInstance.cfg.AddConfigPath("$HOME/.inetmock")
configInstance.cfg.AddConfigPath(".") configInstance.cfg.AddConfigPath(".")
configInstance.cfg.SetEnvPrefix("INetMock") configInstance.cfg.SetEnvPrefix("INetMock")
_ = configInstance.cfg.BindPFlags(flags)
configInstance.cfg.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) configInstance.cfg.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
configInstance.cfg.AutomaticEnv() configInstance.cfg.AutomaticEnv()
@ -41,17 +37,15 @@ func CreateConfig(flags *pflag.FlagSet) Config {
type Config interface { type Config interface {
ReadConfig(configFilePath string) error ReadConfig(configFilePath string) error
ReadConfigString(config, format string) error ReadConfigString(config, format string) error
Viper() *viper.Viper TLSConfig() cert.CertOptions
TLSConfig() CertOptions
APIConfig() RPC APIConfig() RPC
EndpointConfigs() map[string]EndpointConfig ListenerSpecs() map[string]endpoint.ListenerSpec
} }
type config struct { type config struct {
cfg *viper.Viper cfg *viper.Viper
logger logging.Logger TLS cert.CertOptions
TLS CertOptions Listeners map[string]endpoint.ListenerSpec
Endpoints map[string]EndpointConfig
API RPC API RPC
} }
@ -69,30 +63,23 @@ func (c *config) ReadConfigString(config, format string) (err error) {
return return
} }
func (c *config) EndpointConfigs() map[string]EndpointConfig { func (c config) ListenerSpecs() map[string]endpoint.ListenerSpec {
return c.Endpoints return c.Listeners
} }
func (c *config) TLSConfig() CertOptions { func (c config) TLSConfig() cert.CertOptions {
return c.TLS return c.TLS
} }
func (c *config) Viper() *viper.Viper {
return c.cfg
}
func (c *config) ReadConfig(configFilePath string) (err error) { func (c *config) ReadConfig(configFilePath string) (err error) {
if configFilePath != "" && path.FileExists(configFilePath) { if configFilePath != "" && path.FileExists(configFilePath) {
c.logger.Info(
"loading config from passed config file path",
zap.String("configFilePath", configFilePath),
)
c.cfg.SetConfigFile(configFilePath) c.cfg.SetConfigFile(configFilePath)
} }
if err = c.cfg.ReadInConfig(); err != nil { if err = c.cfg.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok { if _, ok := err.(viper.ConfigFileNotFoundError); ok {
err = nil err = nil
c.logger.Warn("failed to load config") } else {
return
} }
} }

View file

@ -0,0 +1,70 @@
package app
import (
"reflect"
"testing"
"gitlab.com/inetmock/inetmock/internal/endpoint"
)
func Test_config_ReadConfig(t *testing.T) {
type args struct {
config string
}
tests := []struct {
name string
args args
wantListeners map[string]endpoint.ListenerSpec
wantErr bool
}{
{
name: "Test endpoints config",
args: args{
//language=yaml
config: `
listeners:
tcp_80:
name: ''
protocol: tcp
listenAddress: ''
port: 80
tcp_443:
name: ''
protocol: tcp
listenAddress: ''
port: 443
`,
},
wantListeners: map[string]endpoint.ListenerSpec{
"tcp_80": {
Name: "",
Protocol: "tcp",
Address: "",
Port: 80,
Endpoints: nil,
},
"tcp_443": {
Name: "",
Protocol: "tcp",
Address: "",
Port: 443,
Endpoints: nil,
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := CreateConfig()
if err := cfg.ReadConfigString(tt.args.config, "yaml"); (err != nil) != tt.wantErr {
t.Errorf("ReadConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(tt.wantListeners, cfg.ListenerSpecs()) {
t.Errorf("want = %v, got = %v", tt.wantListeners, cfg.ListenerSpecs())
}
})
}
}

View file

@ -0,0 +1,6 @@
package app
const (
EndpointsKey = "endpoints"
OptionsKey = "options"
)

View file

@ -1,4 +1,4 @@
package config package app
var ( var (
registeredDefaults = make(map[string]interface{}) registeredDefaults = make(map[string]interface{})

View file

@ -1,4 +1,4 @@
package config package app
import "net/url" import "net/url"

View file

@ -1,4 +1,4 @@
package config package app
import ( import (
"net/url" "net/url"

31
internal/endpoint/api.go Normal file
View file

@ -0,0 +1,31 @@
//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/endpoint/protocol_handler.mock.go -package=endpoint_mock
package endpoint
import (
"context"
"github.com/soheilhy/cmux"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
type Lifecycle interface {
Name() string
Logger() logging.Logger
CertStore() cert.Store
Audit() audit.Emitter
Context() context.Context
Uplink() Uplink
UnmarshalOptions(cfg interface{}) error
}
type ProtocolHandler interface {
Start(ctx Lifecycle) error
}
type MultiplexHandler interface {
ProtocolHandler
Matchers() []cmux.Matcher
}

View file

@ -1,55 +1,42 @@
//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/endpoints/endpoint.mock.go -package=endpoints_mock
package endpoint package endpoint
import ( import (
"context" "context"
"time"
"github.com/google/uuid" "go.uber.org/zap"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/pkg/config"
) )
type Endpoint interface { const (
Id() uuid.UUID startupTimeoutDuration = 100 * time.Millisecond
Start(ctx api.PluginContext) error )
Shutdown(ctx context.Context) error
Name() string type Endpoint struct {
Handler() string Spec
Listen() string name string
Port() uint16 uplink Uplink
} }
type endpoint struct { func (e Endpoint) Start(lifecycle Lifecycle) (err error) {
id uuid.UUID startupResult := make(chan error)
name string ctx, cancel := context.WithTimeout(lifecycle.Context(), startupTimeoutDuration)
handler api.ProtocolHandler defer cancel()
config config.HandlerConfig
}
func (e endpoint) Id() uuid.UUID { go func() {
return e.id defer func() {
} if r := recover(); r != nil {
lifecycle.Logger().Fatal("Startup error recovered", zap.Any("recovered", r))
}
}()
func (e endpoint) Name() string { startupResult <- e.Handler.Start(lifecycle)
return e.name }()
}
func (e endpoint) Handler() string { select {
return e.config.HandlerName case err = <-startupResult:
} case <-ctx.Done():
err = ErrStartupTimeout
}
func (e endpoint) Listen() string { return
return e.config.ListenAddress
}
func (e endpoint) Port() uint16 {
return e.config.Port
}
func (e *endpoint) Start(ctx api.PluginContext) (err error) {
return e.handler.Start(ctx, e.config)
}
func (e *endpoint) Shutdown(ctx context.Context) (err error) {
return e.handler.Shutdown(ctx)
} }

View file

@ -1,179 +0,0 @@
package endpoint
import (
"context"
"fmt"
"sync"
"time"
"github.com/google/uuid"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/health"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
const (
startupTimeoutDuration = 100 * time.Millisecond
)
type EndpointManager interface {
RegisteredEndpoints() []Endpoint
StartedEndpoints() []Endpoint
CreateEndpoint(name string, multiHandlerConfig config.EndpointConfig) error
StartEndpoints()
ShutdownEndpoints()
}
func NewEndpointManager(registry api.HandlerRegistry, logging logging.Logger, checker health.Checker, pluginContext api.PluginContext) EndpointManager {
return &endpointManager{
registry: registry,
logger: logging,
checker: checker,
pluginContext: pluginContext,
}
}
type endpointManager struct {
registry api.HandlerRegistry
logger logging.Logger
checker health.Checker
pluginContext api.PluginContext
registeredEndpoints []Endpoint
properlyStartedEndpoints []Endpoint
}
func (e endpointManager) RegisteredEndpoints() []Endpoint {
return e.registeredEndpoints
}
func (e endpointManager) StartedEndpoints() []Endpoint {
return e.properlyStartedEndpoints
}
func (e *endpointManager) CreateEndpoint(name string, endpointConfig config.EndpointConfig) error {
for _, handlerConfig := range endpointConfig.HandlerConfigs() {
if handler, ok := e.registry.HandlerForName(endpointConfig.Handler); ok {
e.registeredEndpoints = append(e.registeredEndpoints, &endpoint{
id: uuid.New(),
name: name,
handler: handler,
config: handlerConfig,
})
} else {
return fmt.Errorf("no matching handler registered for names %s", endpointConfig.Handler)
}
}
return nil
}
func (e *endpointManager) StartEndpoints() {
startTime := time.Now()
for _, endpoint := range e.registeredEndpoints {
endpointLogger := e.logger.With(
zap.String("endpoint", endpoint.Name()),
)
endpointLogger.Info("Starting endpoint")
if ok := startEndpoint(endpoint, e.pluginContext, endpointLogger); ok {
_ = e.checker.RegisterCheck(
endpointComponentName(endpoint),
health.StaticResultCheckWithMessage(health.HEALTHY, "Successfully started"),
)
e.properlyStartedEndpoints = append(e.properlyStartedEndpoints, endpoint)
endpointLogger.Info("successfully started endpoint")
} else {
_ = e.checker.RegisterCheck(
endpointComponentName(endpoint),
health.StaticResultCheckWithMessage(health.UNHEALTHY, "failed to start"),
)
endpointLogger.Error("error occurred during endpoint startup - will be skipped for now")
}
}
endpointStartupDuration := time.Since(startTime)
e.logger.Info(
"Startup of all endpoints completed",
zap.Duration("startupTime", endpointStartupDuration),
)
}
func (e *endpointManager) ShutdownEndpoints() {
waitGroup := new(sync.WaitGroup)
waitGroup.Add(len(e.properlyStartedEndpoints))
parentCtx, _ := context.WithTimeout(context.Background(), shutdownTimeout)
perHandlerTimeout := e.shutdownTimePerEndpoint()
for _, endpoint := range e.properlyStartedEndpoints {
ctx, _ := context.WithTimeout(parentCtx, perHandlerTimeout)
endpointLogger := e.logger.With(
zap.String("endpoint", endpoint.Name()),
)
endpointLogger.Info("Triggering shutdown of endpoint")
go shutdownEndpoint(ctx, endpoint, endpointLogger, waitGroup)
}
waitGroup.Wait()
}
func startEndpoint(ep Endpoint, ctx api.PluginContext, logger logging.Logger) (success bool) {
startSuccessful := make(chan bool)
go func() {
defer func() {
if r := recover(); r != nil {
logger.Fatal(
"recovered panic during startup of endpoint",
zap.Any("recovered", r),
)
startSuccessful <- false
}
}()
if err := ep.Start(ctx); err != nil {
logger.Error(
"failed to start endpoint",
zap.Error(err),
)
startSuccessful <- false
} else {
startSuccessful <- true
}
}()
select {
case success = <-startSuccessful:
case <-time.After(startupTimeoutDuration):
success = false
}
return
}
func shutdownEndpoint(ctx context.Context, ep Endpoint, logger logging.Logger, wg *sync.WaitGroup) {
defer func() {
if r := recover(); r != nil {
logger.Fatal(
"recovered panic during shutdown of endpoint",
zap.Any("recovered", r),
)
}
wg.Done()
}()
if err := ep.Shutdown(ctx); err != nil {
logger.Error(
"Failed to shutdown endpoint",
zap.Error(err),
)
}
}
func (e *endpointManager) shutdownTimePerEndpoint() time.Duration {
return time.Duration((float64(shutdownTimeout) * 0.9) / float64(len(e.properlyStartedEndpoints)))
}
func endpointComponentName(ep Endpoint) string {
return fmt.Sprintf("endpoint_%s", ep.Name())
}

View file

@ -1,139 +0,0 @@
package endpoint
import (
"reflect"
"testing"
"github.com/golang/mock/gomock"
api_mock "gitlab.com/inetmock/inetmock/internal/mock/api"
logging_mock "gitlab.com/inetmock/inetmock/internal/mock/logging"
plugins_mock "gitlab.com/inetmock/inetmock/internal/mock/plugins"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/health"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
func Test_endpointManager_CreateEndpoint(t *testing.T) {
type fields struct {
logger logging.Logger
registry api.HandlerRegistry
}
type args struct {
name string
multiHandlerConfig config.EndpointConfig
}
tests := []struct {
name string
fields fields
args args
wantErr bool
wantEndpoints int
}{
{
name: "Test add endpoint",
wantErr: false,
wantEndpoints: 1,
fields: fields{
logger: func() logging.Logger {
return logging_mock.NewMockLogger(gomock.NewController(t))
}(),
registry: func() api.HandlerRegistry {
registry := plugins_mock.NewMockHandlerRegistry(gomock.NewController(t))
registry.
EXPECT().
HandlerForName("sampleHandler").
MinTimes(1).
MaxTimes(1).
Return(api_mock.NewMockProtocolHandler(gomock.NewController(t)), true)
return registry
}(),
},
args: args{
name: "sampleEndpoint",
multiHandlerConfig: config.EndpointConfig{
Handler: "sampleHandler",
Ports: []uint16{80},
ListenAddress: "0.0.0.0",
},
},
},
{
name: "Test add unknown handler",
wantErr: true,
wantEndpoints: 0,
fields: fields{
logger: func() logging.Logger {
return logging_mock.NewMockLogger(gomock.NewController(t))
}(),
registry: func() api.HandlerRegistry {
registry := plugins_mock.NewMockHandlerRegistry(gomock.NewController(t))
registry.
EXPECT().
HandlerForName("sampleHandler").
MinTimes(1).
MaxTimes(1).
Return(nil, false)
return registry
}(),
},
args: args{
name: "sampleEndpoint",
multiHandlerConfig: config.EndpointConfig{
Handler: "sampleHandler",
Ports: []uint16{80},
ListenAddress: "0.0.0.0",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := NewEndpointManager(tt.fields.registry, tt.fields.logger, health.New(), nil)
if err := e.CreateEndpoint(tt.args.name, tt.args.multiHandlerConfig); (err != nil) != tt.wantErr {
t.Errorf("CreateEndpoint() error = %v, wantErr %v", err, tt.wantErr)
}
if len(e.RegisteredEndpoints()) != tt.wantEndpoints {
t.Errorf("RegisteredEndpoints() = %d, want = 1", len(e.RegisteredEndpoints()))
return
}
if len(e.RegisteredEndpoints()) > 0 && e.RegisteredEndpoints()[0].Name() != tt.args.name {
t.Errorf("Name() = %s, want = %s", e.RegisteredEndpoints()[0].Name(), tt.args.name)
}
})
}
}
func Test_endpointManager_StartedEndpoints(t *testing.T) {
type fields struct {
logger logging.Logger
registeredEndpoints []Endpoint
properlyStartedEndpoints []Endpoint
registry api.HandlerRegistry
}
tests := []struct {
name string
fields fields
want []Endpoint
}{
{
name: "",
fields: fields{},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := endpointManager{
logger: tt.fields.logger,
registeredEndpoints: tt.fields.registeredEndpoints,
properlyStartedEndpoints: tt.fields.properlyStartedEndpoints,
}
if got := e.StartedEndpoints(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("StartedEndpoints() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -7,38 +7,45 @@ import (
"net" "net"
"unsafe" "unsafe"
"github.com/spf13/viper" "github.com/mitchellh/mapstructure"
) )
const ( const (
randomIPStrategyName = "random" randomIPStrategyName = "random"
incrementalIPStrategyName = "incremental" incrementalIPStrategyName = "incremental"
startIPConfigKey = "startIP"
) )
var ( var (
fallbackStrategies map[string]ResolverFactory defaultStartIPIncrementalStrategy = net.ParseIP("10.10.0.1")
fallbackStrategies = map[string]ResolverFactory{
incrementalIPStrategyName: func(args map[string]interface{}) ResolverFallback {
tmp := struct {
StartIP string
}{}
var startIp net.IP
if err := mapstructure.Decode(args, &tmp); err == nil {
startIp = net.ParseIP(tmp.StartIP)
}
if startIp == nil || len(startIp) == 0 {
startIp = defaultStartIPIncrementalStrategy
}
return &incrementalIPFallback{
latestIp: ipToInt32(startIp),
}
},
randomIPStrategyName: func(map[string]interface{}) ResolverFallback {
return &randomIPFallback{}
},
}
) )
type ResolverFactory func(conf *viper.Viper) ResolverFallback type ResolverFactory func(args map[string]interface{}) ResolverFallback
func init() { func CreateResolverFallback(name string, args map[string]interface{}) ResolverFallback {
fallbackStrategies = make(map[string]ResolverFactory)
fallbackStrategies[incrementalIPStrategyName] = func(conf *viper.Viper) ResolverFallback {
return &incrementalIPFallback{
latestIp: ipToInt32(net.ParseIP(conf.GetString(startIPConfigKey))),
}
}
fallbackStrategies[randomIPStrategyName] = func(conf *viper.Viper) ResolverFallback {
return &randomIPFallback{}
}
}
func CreateResolverFallback(name string, config *viper.Viper) ResolverFallback {
if factory, ok := fallbackStrategies[name]; ok { if factory, ok := fallbackStrategies[name]; ok {
return factory(config) return factory(args)
} else { } else {
return fallbackStrategies[randomIPStrategyName](config) return fallbackStrategies[randomIPStrategyName](args)
} }
} }

View file

@ -2,36 +2,35 @@ package mock
import ( import (
"context" "context"
"time"
"github.com/miekg/dns" "github.com/miekg/dns"
"gitlab.com/inetmock/inetmock/pkg/api" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )
type dnsHandler struct { type dnsHandler struct {
logger logging.Logger logger logging.Logger
dnsServer []*dns.Server dnsServer *dns.Server
} }
func (d *dnsHandler) Start(pluginCtx api.PluginContext, config config.HandlerConfig) (err error) { func (d *dnsHandler) Start(lifecycle endpoint.Lifecycle) (err error) {
var options dnsOptions var options dnsOptions
if options, err = loadFromConfig(config.Options); err != nil { if options, err = loadFromConfig(lifecycle); err != nil {
return return
} }
listenAddr := config.ListenAddr() d.logger = lifecycle.Logger().With(
d.logger = pluginCtx.Logger().With( zap.String("handler_name", lifecycle.Name()),
zap.String("handler_name", config.HandlerName), zap.String("address", lifecycle.Uplink().Addr().String()),
zap.String("address", listenAddr),
) )
handler := &regexHandler{ handler := &regexHandler{
handlerName: config.HandlerName, handlerName: lifecycle.Name(),
fallback: options.Fallback, fallback: options.Fallback,
logger: pluginCtx.Logger(), logger: lifecycle.Logger(),
auditEmitter: pluginCtx.Audit(), auditEmitter: lifecycle.Audit(),
} }
for _, rule := range options.Rules { for _, rule := range options.Rules {
@ -43,31 +42,24 @@ func (d *dnsHandler) Start(pluginCtx api.PluginContext, config config.HandlerCon
handler.AddRule(rule) handler.AddRule(rule)
} }
d.logger = d.logger.With( if lifecycle.Uplink().Listener != nil {
zap.String("address", listenAddr), d.dnsServer = &dns.Server{
) Listener: lifecycle.Uplink().Listener,
Handler: handler,
d.dnsServer = []*dns.Server{ }
{ } else {
Addr: listenAddr, d.dnsServer = &dns.Server{
Net: "udp", PacketConn: lifecycle.Uplink().PacketConn,
Handler: handler, Handler: handler,
}, }
{
Addr: listenAddr,
Net: "tcp",
Handler: handler,
},
} }
for _, dnsServer := range d.dnsServer { go d.startServer()
go d.startServer(dnsServer)
}
return return
} }
func (d *dnsHandler) startServer(dnsServer *dns.Server) { func (d *dnsHandler) startServer() {
if err := dnsServer.ListenAndServe(); err != nil { if err := d.dnsServer.ActivateAndServe(); err != nil {
d.logger.Error( d.logger.Error(
"failed to start DNS server listener", "failed to start DNS server listener",
zap.Error(err), zap.Error(err),
@ -75,15 +67,11 @@ func (d *dnsHandler) startServer(dnsServer *dns.Server) {
} }
} }
func (d *dnsHandler) Shutdown(ctx context.Context) error { func (d *dnsHandler) shutdownOnEnd(ctx context.Context) {
d.logger.Info("shutting down DNS mock") <-ctx.Done()
for _, dnsServer := range d.dnsServer { shutdownCtx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
if err := dnsServer.ShutdownContext(ctx); err != nil { defer cancel()
d.logger.Error( if err := d.dnsServer.ShutdownContext(shutdownCtx); err != nil {
"failed to shutdown server", d.logger.Error("failed to shutdown DNS server", zap.Error(err))
zap.Error(err),
)
}
} }
return nil
} }

View file

@ -0,0 +1,77 @@
package mock
import (
"context"
"fmt"
"math/rand"
"net"
"strings"
"testing"
"time"
"github.com/docker/go-connections/nat"
"github.com/testcontainers/testcontainers-go"
"gitlab.com/inetmock/inetmock/internal/test/integration"
)
const (
charSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP"
)
func init() {
rand.Seed(time.Now().Unix())
}
func Benchmark_dnsHandler(b *testing.B) {
var err error
var endpoint string
if endpoint, err = setupContainer(b, "53/udp"); err != nil {
b.Errorf("setupContainer() error = %v", err)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
resolv := resolver(endpoint)
for pb.Next() {
lookupCtx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
_, err := resolv.LookupHost(lookupCtx, fmt.Sprintf("www.%s.com", randomString(8)))
cancel()
if err != nil {
b.Errorf("LookupHost() error = %v", err)
}
}
})
}
func randomString(length int) (result string) {
buffer := strings.Builder{}
for i := 0; i < length; i++ {
buffer.WriteByte(charSet[rand.Intn(len(charSet))])
}
return buffer.String()
}
func setupContainer(b *testing.B, port string) (httpEndpoint string, err error) {
b.Helper()
startupCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
var inetMockContainer testcontainers.Container
if inetMockContainer, err = integration.SetupINetMockContainer(startupCtx, b, port); err != nil {
return
}
httpEndpoint, err = inetMockContainer.PortEndpoint(startupCtx, nat.Port(port), "")
return
}
func resolver(endpoint string) net.Resolver {
return net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (conn net.Conn, err error) {
dialer := net.Dialer{}
return dialer.DialContext(ctx, "udp", endpoint)
},
}
}

View file

@ -4,11 +4,7 @@ import (
"net" "net"
"regexp" "regexp"
"github.com/spf13/viper" "gitlab.com/inetmock/inetmock/internal/endpoint"
)
const (
fallbackArgsConfigKey = "fallback.args"
) )
type resolverRule struct { type resolverRule struct {
@ -21,7 +17,7 @@ type dnsOptions struct {
Fallback ResolverFallback Fallback ResolverFallback
} }
func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) { func loadFromConfig(lifecycle endpoint.Lifecycle) (options dnsOptions, err error) {
type rule struct { type rule struct {
Pattern string Pattern string
Response string Response string
@ -29,6 +25,7 @@ func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) {
type fallback struct { type fallback struct {
Strategy string Strategy string
Args map[string]interface{}
} }
opts := struct { opts := struct {
@ -36,7 +33,7 @@ func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) {
Fallback fallback Fallback fallback
}{} }{}
err = config.Unmarshal(&opts) err = lifecycle.UnmarshalOptions(&opts)
for _, rule := range opts.Rules { for _, rule := range opts.Rules {
var err error var err error
@ -53,7 +50,7 @@ func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) {
options.Fallback = CreateResolverFallback( options.Fallback = CreateResolverFallback(
opts.Fallback.Strategy, opts.Fallback.Strategy,
config.Sub(fallbackArgsConfigKey), opts.Fallback.Args,
) )
return return

View file

@ -2,7 +2,7 @@ package mock
import ( import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/pkg/api" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/metrics" "gitlab.com/inetmock/inetmock/pkg/metrics"
) )
@ -17,7 +17,7 @@ var (
requestDurationHistogram *prometheus.HistogramVec requestDurationHistogram *prometheus.HistogramVec
) )
func AddDNSMock(registry api.HandlerRegistry) (err error) { func AddDNSMock(registry endpoint.HandlerRegistry) (err error) {
if totalHandledRequestsCounter, err = metrics.Counter( if totalHandledRequestsCounter, err = metrics.Counter(
name, name,
"handled_requests_total", "handled_requests_total",
@ -46,7 +46,7 @@ func AddDNSMock(registry api.HandlerRegistry) (err error) {
return return
} }
registry.RegisterHandler(name, func() api.ProtocolHandler { registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &dnsHandler{} return &dnsHandler{}
}) })

View file

@ -23,11 +23,11 @@ func EventFromRequest(request *http.Request, app audit.AppProtocol) audit.Event
ProtocolDetails: httpDetails, ProtocolDetails: httpDetails,
} }
if request.TLS != nil { if state, ok := tlsConnectionState(request.Context()); ok {
ev.TLS = &audit.TLSDetails{ ev.TLS = &audit.TLSDetails{
Version: audit.TLSVersionToEntity(request.TLS.Version).String(), Version: audit.TLSVersionToEntity(state.Version).String(),
CipherSuite: tls.CipherSuiteName(request.TLS.CipherSuite), CipherSuite: tls.CipherSuiteName(state.CipherSuite),
ServerName: request.TLS.ServerName, ServerName: state.ServerName,
} }
} }

View file

@ -2,7 +2,10 @@ package http
import ( import (
"context" "context"
"crypto/tls"
"net" "net"
"github.com/soheilhy/cmux"
) )
type httpContextKey string type httpContextKey string
@ -10,14 +13,35 @@ type httpContextKey string
const ( const (
remoteAddrKey httpContextKey = "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/context/remoteAddr" remoteAddrKey httpContextKey = "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/context/remoteAddr"
localAddrKey httpContextKey = "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/context/localAddr" localAddrKey httpContextKey = "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/context/localAddr"
tlsStateKey httpContextKey = "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/context/tlsState"
) )
func StoreConnPropertiesInContext(ctx context.Context, c net.Conn) context.Context { func StoreConnPropertiesInContext(ctx context.Context, c net.Conn) context.Context {
ctx = context.WithValue(ctx, remoteAddrKey, c.RemoteAddr()) ctx = context.WithValue(ctx, remoteAddrKey, c.RemoteAddr())
ctx = context.WithValue(ctx, localAddrKey, c.LocalAddr()) ctx = context.WithValue(ctx, localAddrKey, c.LocalAddr())
ctx = addTLSConnectionStateToContext(ctx, c)
return ctx return ctx
} }
func addTLSConnectionStateToContext(ctx context.Context, c net.Conn) context.Context {
switch subConn := c.(type) {
case *tls.Conn:
return context.WithValue(ctx, tlsStateKey, subConn.ConnectionState())
case *cmux.MuxConn:
return addTLSConnectionStateToContext(ctx, subConn.Conn)
default:
return ctx
}
}
func tlsConnectionState(ctx context.Context) (tls.ConnectionState, bool) {
val := ctx.Value(tlsStateKey)
if val == nil {
return tls.ConnectionState{}, false
}
return val.(tls.ConnectionState), true
}
func localAddr(ctx context.Context) net.Addr { func localAddr(ctx context.Context) net.Addr {
val := ctx.Value(localAddrKey) val := ctx.Value(localAddrKey)
if val == nil { if val == nil {

View file

@ -2,14 +2,13 @@ package mock
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "net"
"net/http" "net/http"
"github.com/soheilhy/cmux"
"gitlab.com/inetmock/inetmock/internal/endpoint"
imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http" imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -25,71 +24,62 @@ type httpHandler struct {
server *http.Server server *http.Server
} }
func (p *httpHandler) Start(ctx api.PluginContext, config config.HandlerConfig) (err error) { func (p *httpHandler) Matchers() []cmux.Matcher {
p.logger = ctx.Logger().With( return []cmux.Matcher{cmux.HTTP1()}
}
func (p *httpHandler) Start(lifecycle endpoint.Lifecycle) (err error) {
p.logger = lifecycle.Logger().With(
zap.String("protocol_handler", name), zap.String("protocol_handler", name),
) )
var options httpOptions var options httpOptions
if options, err = loadFromConfig(config.Options); err != nil { if options, err = loadFromConfig(lifecycle); err != nil {
return return
} }
p.logger = p.logger.With( p.logger = p.logger.With(
zap.String("handler_name", config.HandlerName), zap.String("address", lifecycle.Uplink().Addr().String()),
zap.String("address", config.ListenAddr()),
) )
router := &RegexpHandler{ router := &RegexpHandler{
logger: p.logger, logger: p.logger,
emitter: ctx.Audit(), emitter: lifecycle.Audit(),
handlerName: config.HandlerName, handlerName: lifecycle.Name(),
} }
p.server = &http.Server{ p.server = &http.Server{
Addr: config.ListenAddr(),
Handler: router, Handler: router,
ConnContext: imHttp.StoreConnPropertiesInContext, ConnContext: imHttp.StoreConnPropertiesInContext,
} }
if options.TLS {
p.server.TLSConfig = ctx.CertStore().TLSConfig()
p.server.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
}
for _, rule := range options.Rules { for _, rule := range options.Rules {
router.setupRoute(rule) router.setupRoute(rule)
} }
go p.startServer(options.TLS) go p.startServer(lifecycle.Uplink().Listener)
go p.shutdownOnCancel(lifecycle.Context())
return return
} }
func (p *httpHandler) Shutdown(ctx context.Context) (err error) { func (p *httpHandler) shutdownOnCancel(ctx context.Context) {
<-ctx.Done()
p.logger.Info("Shutting down HTTP mock") p.logger.Info("Shutting down HTTP mock")
if err = p.server.Shutdown(ctx); err != nil { if err := p.server.Close(); err != nil {
p.logger.Error( p.logger.Error(
"failed to shutdown HTTP server", "failed to shutdown HTTP server",
zap.Error(err), zap.Error(err),
) )
err = fmt.Errorf(
"failed to shutdown HTTP server: %w",
err,
)
} }
return return
} }
func (p *httpHandler) startServer(tls bool) { func (p *httpHandler) startServer(listener net.Listener) {
var listen func() error defer func() {
if tls { if err := listener.Close(); err != nil {
listen = func() error { p.logger.Warn("failed to close listener", zap.Error(err))
return p.server.ListenAndServeTLS("", "")
} }
} else { }()
listen = p.server.ListenAndServe if err := p.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
}
if err := listen(); err != nil && !errors.Is(err, http.ErrServerClosed) {
p.logger.Error( p.logger.Error(
"failed to start http listener", "failed to start http listener",
zap.Error(err), zap.Error(err),

View file

@ -0,0 +1,155 @@
package mock_test
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/docker/go-connections/nat"
"github.com/testcontainers/testcontainers-go"
"gitlab.com/inetmock/inetmock/internal/test/integration"
)
const (
charSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP"
)
var (
availableExtensions = []string{"gif", "html", "ico", "jpg", "png", "txt"}
)
func init() {
rand.Seed(time.Now().Unix())
}
func Benchmark_httpHandler(b *testing.B) {
type benchmark struct {
name string
port string
scheme string
}
benchmarks := []benchmark{
{
name: "HTTP",
port: "80/tcp",
scheme: "http",
},
{
name: "HTTPS",
port: "443/tcp",
scheme: "https",
},
}
scenario := func(bm benchmark) func(bm *testing.B) {
return func(b *testing.B) {
var err error
var endpoint string
if endpoint, err = setupContainer(b, bm.scheme, bm.port); err != nil {
b.Errorf("setupContainer() error = %v", err)
}
var httpClient *http.Client
if httpClient, err = setupHTTPClient(); err != nil {
return
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
extension := availableExtensions[rand.Intn(len(availableExtensions))]
reqUrl, _ := url.Parse(fmt.Sprintf("%s/%s.%s", endpoint, randomString(15), extension))
req := &http.Request{
Method: http.MethodGet,
URL: reqUrl,
Close: false,
Host: "www.inetmock.com",
}
if resp, err := httpClient.Do(req); err != nil {
b.Error(err)
} else if resp.StatusCode != 200 {
b.Errorf("Got status code %d", resp.StatusCode)
}
}
})
}
}
for _, bm := range benchmarks {
b.Run(bm.name, scenario(bm))
}
}
func randomString(length int) (result string) {
buffer := strings.Builder{}
for i := 0; i < length; i++ {
buffer.WriteByte(charSet[rand.Intn(len(charSet))])
}
return buffer.String()
}
func setupContainer(b *testing.B, scheme, port string) (httpEndpoint string, err error) {
b.Helper()
startupCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
var inetMockContainer testcontainers.Container
if inetMockContainer, err = integration.SetupINetMockContainer(startupCtx, b, port); err != nil {
return
}
httpEndpoint, err = inetMockContainer.PortEndpoint(startupCtx, nat.Port(port), scheme)
return
}
func setupHTTPClient() (client *http.Client, err error) {
_, fileName, _, _ := runtime.Caller(0)
var repoRoot string
if repoRoot, err = filepath.Abs(filepath.Join(filepath.Dir(fileName), "..", "..", "..", "..", "..")); err != nil {
return
}
var demoCABytes []byte
if demoCABytes, err = ioutil.ReadFile(filepath.Join(repoRoot, "assets", "demoCA", "ca.pem")); err != nil {
return
}
rootCaPool := x509.NewCertPool()
if !rootCaPool.AppendCertsFromPEM(demoCABytes) {
err = errors.New("failed to add CA key")
return
}
client = &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSClientConfig: &tls.Config{
RootCAs: rootCaPool,
},
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
return
}

View file

@ -1,147 +0,0 @@
package mock_test
import (
"context"
"fmt"
"math/rand"
"net"
"net/http"
"strconv"
"strings"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/spf13/viper"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
api_mock "gitlab.com/inetmock/inetmock/internal/mock/api"
audit_mock "gitlab.com/inetmock/inetmock/internal/mock/audit"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
var (
testLogger = logging.NewLogger(zap.NewNop())
availableExtensions = []string{"gif", "html", "ico", "jpg", "png", "txt"}
charSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP"
)
func init() {
rand.Seed(time.Now().Unix())
}
func Benchmark_httpHandler(b *testing.B) {
ctrl := gomock.NewController(b)
defer ctrl.Finish()
listenPort := randomHighPort()
_, handler := setupHandler(b, ctrl, listenPort)
b.ResetTimer()
for i := 0; i < b.N; i++ {
extension := availableExtensions[rand.Intn(len(availableExtensions))]
if resp, err := http.Get(fmt.Sprintf("http://localhost:%d/%s.%s", listenPort, randomString(15), extension)); err != nil {
b.Error(err)
} else if resp.StatusCode != 200 {
b.Error("")
}
}
defer handler.Shutdown(context.Background())
}
func randomString(length int) (result string) {
buffer := strings.Builder{}
for i := 0; i < length; i++ {
buffer.WriteByte(charSet[rand.Intn(len(charSet))])
}
return buffer.String()
}
func setupHandler(b *testing.B, ctrl *gomock.Controller, listenPort uint16) (api.HandlerRegistry, api.ProtocolHandler) {
b.Helper()
registry := api.NewHandlerRegistry()
if err := mock.AddHTTPMock(registry); err != nil {
b.Errorf("AddHTTPMock() error = %v", err)
}
handler, ok := registry.HandlerForName("http_mock")
if !ok {
b.Error("handler not registered")
}
emitter := audit_mock.NewMockEmitter(ctrl)
emitter.EXPECT().Emit(gomock.Any()).AnyTimes()
mockApp := api_mock.NewMockPluginContext(ctrl)
mockApp.EXPECT().
Logger().
Return(testLogger)
mockApp.EXPECT().
Audit().
Return(emitter)
v := viper.New()
v.Set("rules", []map[string]string{
{
"pattern": ".*\\.(?i)gif",
"response": "./../../assets/fakeFiles/default.gif",
},
{
"pattern": ".*\\.(?i)html",
"response": "./../../assets/fakeFiles/default.html",
},
{
"pattern": ".*\\.(?i)ico",
"response": "./../../assets/fakeFiles/default.ico",
},
{
"pattern": ".*\\.(?i)jpg",
"response": "./../../assets/fakeFiles/default.jpg",
},
{
"pattern": ".*\\.(?i)png",
"response": "./../../assets/fakeFiles/default.png",
},
{
"pattern": ".*\\.(?i)txt",
"response": "./../../assets/fakeFiles/default.txt",
},
})
handlerConfig := config.HandlerConfig{
HandlerName: "http_test",
Port: listenPort,
ListenAddress: "localhost",
Options: v,
}
if err := handler.Start(mockApp, handlerConfig); err != nil {
b.Error(err)
b.FailNow()
}
return registry, handler
}
func randomHighPort() uint16 {
var err error
var listener net.Listener
defer func() {
if listener != nil {
_ = listener.Close()
}
}()
for {
if listener, err = net.Listen("tcp", ":0"); err == nil {
parts := strings.Split(listener.Addr().String(), ":")
port, _ := strconv.Atoi(parts[len(parts)-1])
return uint16(port)
}
}
}

View file

@ -6,7 +6,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"github.com/spf13/viper" "gitlab.com/inetmock/inetmock/internal/endpoint"
) )
var ( var (
@ -50,11 +50,10 @@ func (tr targetRule) Response() string {
} }
type httpOptions struct { type httpOptions struct {
TLS bool
Rules []targetRule Rules []targetRule
} }
func loadFromConfig(config *viper.Viper) (options httpOptions, err error) { func loadFromConfig(lifecycle endpoint.Lifecycle) (options httpOptions, err error) {
type tmpCfg struct { type tmpCfg struct {
Pattern string Pattern string
Response string Response string
@ -63,16 +62,13 @@ func loadFromConfig(config *viper.Viper) (options httpOptions, err error) {
} }
tmpRules := struct { tmpRules := struct {
TLS bool
Rules []tmpCfg Rules []tmpCfg
}{} }{}
if err = config.Unmarshal(&tmpRules); err != nil { if err = lifecycle.UnmarshalOptions(&tmpRules); err != nil {
return return
} }
options.TLS = tmpRules.TLS
for _, i := range tmpRules.Rules { for _, i := range tmpRules.Rules {
var rulePattern *regexp.Regexp var rulePattern *regexp.Regexp
var matchTargetValue RequestMatchTarget var matchTargetValue RequestMatchTarget

View file

@ -4,15 +4,16 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp" "regexp"
"strings"
"testing" "testing"
"github.com/spf13/viper" "github.com/golang/mock/gomock"
"github.com/mitchellh/mapstructure"
endpoint_mock "gitlab.com/inetmock/inetmock/internal/mock/endpoint"
) )
func Test_loadFromConfig(t *testing.T) { func Test_loadFromConfig(t *testing.T) {
type args struct { type args struct {
config string config map[string]interface{}
} }
tests := []struct { tests := []struct {
name string name string
@ -23,11 +24,18 @@ func Test_loadFromConfig(t *testing.T) {
{ {
name: "Parse default config", name: "Parse default config",
args: args{ args: args{
config: ` config: map[string]interface{}{
rules: "rules": []struct {
- pattern: ".*\\.(?i)exe" Pattern string
response: ./assets/fakeFiles/sample.exe Matcher string
`, Response string
}{
{
Pattern: ".*\\.(?i)exe",
Response: "./assets/fakeFiles/sample.exe",
},
},
},
}, },
wantOptions: httpOptions{ wantOptions: httpOptions{
Rules: []targetRule{ Rules: []targetRule{
@ -47,12 +55,19 @@ rules:
{ {
name: "Parse config with path matcher", name: "Parse config with path matcher",
args: args{ args: args{
config: ` config: map[string]interface{}{
rules: "rules": []struct {
- pattern: ".*\\.(?i)exe" Pattern string
matcher: Path Matcher string
response: ./assets/fakeFiles/sample.exe Response string
`, }{
{
Pattern: ".*\\.(?i)exe",
Response: "./assets/fakeFiles/sample.exe",
Matcher: "Path",
},
},
},
}, },
wantOptions: httpOptions{ wantOptions: httpOptions{
Rules: []targetRule{ Rules: []targetRule{
@ -72,13 +87,21 @@ rules:
{ {
name: "Parse config with header matcher", name: "Parse config with header matcher",
args: args{ args: args{
config: ` config: map[string]interface{}{
rules: "rules": []struct {
- pattern: "^application/octet-stream$" Pattern string
target: Content-Type Matcher string
matcher: Header Target string
response: ./assets/fakeFiles/sample.exe Response string
`, }{
{
Pattern: "^application/octet-stream$",
Response: "./assets/fakeFiles/sample.exe",
Target: "Content-Type",
Matcher: "Header",
},
},
},
}, },
wantOptions: httpOptions{ wantOptions: httpOptions{
Rules: []targetRule{ Rules: []targetRule{
@ -98,17 +121,24 @@ rules:
{ {
name: "Parse config with header matcher and TLS true", name: "Parse config with header matcher and TLS true",
args: args{ args: args{
config: ` config: map[string]interface{}{
tls: true "tls": true,
rules: "rules": []struct {
- pattern: "^application/octet-stream$" Pattern string
target: Content-Type Matcher string
matcher: Header Target string
response: ./assets/fakeFiles/sample.exe Response string
`, }{
{
Pattern: "^application/octet-stream$",
Response: "./assets/fakeFiles/sample.exe",
Target: "Content-Type",
Matcher: "Header",
},
},
},
}, },
wantOptions: httpOptions{ wantOptions: httpOptions{
TLS: true,
Rules: []targetRule{ Rules: []targetRule{
{ {
pattern: regexp.MustCompile("^application/octet-stream$"), pattern: regexp.MustCompile("^application/octet-stream$"),
@ -126,10 +156,15 @@ rules:
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
v := viper.New() ctrl := gomock.NewController(t)
v.SetConfigType("yaml") t.Cleanup(ctrl.Finish)
_ = v.ReadConfig(strings.NewReader(tt.args.config)) lcMock := endpoint_mock.NewMockLifecycle(ctrl)
gotOptions, err := loadFromConfig(v)
lcMock.EXPECT().UnmarshalOptions(gomock.Any()).Do(func(cfg interface{}) {
_ = mapstructure.Decode(tt.args.config, cfg)
})
gotOptions, err := loadFromConfig(lcMock)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("loadFromConfig() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("loadFromConfig() error = %v, wantErr %v", err, tt.wantErr)
return return

View file

@ -2,7 +2,7 @@ package mock
import ( import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/pkg/api" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/metrics" "gitlab.com/inetmock/inetmock/pkg/metrics"
) )
@ -11,7 +11,7 @@ var (
requestDurationHistogram *prometheus.HistogramVec requestDurationHistogram *prometheus.HistogramVec
) )
func AddHTTPMock(registry api.HandlerRegistry) (err error) { func AddHTTPMock(registry endpoint.HandlerRegistry) (err error) {
if totalRequestCounter == nil { if totalRequestCounter == nil {
if totalRequestCounter, err = metrics.Counter( if totalRequestCounter, err = metrics.Counter(
name, name,
@ -36,7 +36,7 @@ func AddHTTPMock(registry api.HandlerRegistry) (err error) {
} }
} }
registry.RegisterHandler(name, func() api.ProtocolHandler { registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &httpHandler{} return &httpHandler{}
}) })

View file

@ -3,12 +3,12 @@ package proxy
import ( import (
"context" "context"
"errors" "errors"
"fmt" "net"
"net/http" "net/http"
"github.com/soheilhy/cmux"
"gitlab.com/inetmock/inetmock/internal/endpoint"
imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http" imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
"gopkg.in/elazarl/goproxy.v1" "gopkg.in/elazarl/goproxy.v1"
@ -24,46 +24,49 @@ type httpProxy struct {
server *http.Server server *http.Server
} }
func (h *httpProxy) Start(ctx api.PluginContext, cfg config.HandlerConfig) (err error) { func (h *httpProxy) Matchers() []cmux.Matcher {
return []cmux.Matcher{cmux.HTTP1()}
}
func (h *httpProxy) Start(lifecycle endpoint.Lifecycle) (err error) {
var opts httpProxyOptions var opts httpProxyOptions
if err = cfg.Options.Unmarshal(&opts); err != nil { if err = lifecycle.UnmarshalOptions(&opts); err != nil {
return return
} }
listenAddr := cfg.ListenAddr()
h.server = &http.Server{ h.server = &http.Server{
Addr: listenAddr,
Handler: h.proxy, Handler: h.proxy,
ConnContext: imHttp.StoreConnPropertiesInContext, ConnContext: imHttp.StoreConnPropertiesInContext,
} }
h.logger = h.logger.With( h.logger = h.logger.With(
zap.String("handler_name", cfg.HandlerName), zap.String("handler_name", lifecycle.Name()),
zap.String("address", listenAddr), zap.String("address", lifecycle.Uplink().Addr().String()),
) )
tlsConfig := ctx.CertStore().TLSConfig() tlsConfig := lifecycle.CertStore().TLSConfig()
proxyHandler := &proxyHttpHandler{ proxyHandler := &proxyHttpHandler{
handlerName: cfg.HandlerName, handlerName: lifecycle.Name(),
options: opts, options: opts,
logger: h.logger, logger: h.logger,
emitter: ctx.Audit(), emitter: lifecycle.Audit(),
} }
proxyHTTPSHandler := &proxyHttpsHandler{ proxyHTTPSHandler := &proxyHttpsHandler{
handlerName: cfg.HandlerName, options: opts,
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
logger: h.logger, emitter: lifecycle.Audit(),
emitter: ctx.Audit(),
} }
h.proxy.OnRequest().Do(proxyHandler) h.proxy.OnRequest().Do(proxyHandler)
h.proxy.OnRequest().HandleConnect(proxyHTTPSHandler) h.proxy.OnRequest().HandleConnect(proxyHTTPSHandler)
go h.startProxy() go h.startProxy(lifecycle.Uplink().Listener)
go h.shutdownOnContextDone(lifecycle.Context())
return return
} }
func (h *httpProxy) startProxy() { func (h *httpProxy) startProxy(listener net.Listener) {
if err := h.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { if err := h.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
h.logger.Error( h.logger.Error(
"failed to start proxy server", "failed to start proxy server",
zap.Error(err), zap.Error(err),
@ -71,18 +74,15 @@ func (h *httpProxy) startProxy() {
} }
} }
func (h *httpProxy) Shutdown(ctx context.Context) (err error) { func (h *httpProxy) shutdownOnContextDone(ctx context.Context) {
<-ctx.Done()
var err error
h.logger.Info("Shutting down HTTP proxy") h.logger.Info("Shutting down HTTP proxy")
if err = h.server.Shutdown(ctx); err != nil { if err = h.server.Close(); err != nil {
h.logger.Error( h.logger.Error(
"failed to shutdown proxy endpoint", "failed to shutdown proxy endpoint",
zap.Error(err), zap.Error(err),
) )
err = fmt.Errorf(
"failed to shutdown proxy endpoint: %w",
err,
)
} }
return return
} }

View file

@ -0,0 +1,167 @@
package proxy_test
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"net/url"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/docker/go-connections/nat"
"github.com/testcontainers/testcontainers-go"
"gitlab.com/inetmock/inetmock/internal/test/integration"
)
const (
charSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP"
)
var (
availableExtensions = []string{"gif", "html", "ico", "jpg", "png", "txt"}
)
func init() {
rand.Seed(time.Now().Unix())
}
func Benchmark_httpProxy(b *testing.B) {
type benchmark struct {
name string
port string
scheme string
}
benchmarks := []benchmark{
{
name: "HTTP",
port: "3128/tcp",
scheme: "http",
},
{
name: "HTTPS",
port: "3128/tcp",
scheme: "https",
},
}
scenario := func(bm benchmark) func(bm *testing.B) {
return func(b *testing.B) {
var err error
var endpoint string
if endpoint, err = setupContainer(b, bm.port); err != nil {
b.Errorf("setupContainer() error = %v", err)
}
var httpClient *http.Client
if httpClient, err = setupHTTPClient(fmt.Sprintf("http://%s", endpoint), fmt.Sprintf("https://%s", endpoint)); err != nil {
return
}
time.Sleep(500 * time.Millisecond)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
extension := availableExtensions[rand.Intn(len(availableExtensions))]
reqUrl, _ := url.Parse(fmt.Sprintf("%s://%s/%s.%s", bm.scheme, endpoint, randomString(15), extension))
req := &http.Request{
Method: http.MethodGet,
URL: reqUrl,
Close: false,
Host: "www.inetmock.com",
}
if resp, err := httpClient.Do(req); err != nil {
b.Error(err)
} else if resp.StatusCode != 200 {
b.Errorf("Got status code %d", resp.StatusCode)
}
}
})
}
}
for _, bm := range benchmarks {
b.Run(bm.name, scenario(bm))
}
}
func randomString(length int) (result string) {
buffer := strings.Builder{}
for i := 0; i < length; i++ {
buffer.WriteByte(charSet[rand.Intn(len(charSet))])
}
return buffer.String()
}
func setupContainer(b *testing.B, port string) (httpEndpoint string, err error) {
b.Helper()
startupCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
var inetMockContainer testcontainers.Container
if inetMockContainer, err = integration.SetupINetMockContainer(startupCtx, b, port); err != nil {
return
}
httpEndpoint, err = inetMockContainer.PortEndpoint(startupCtx, nat.Port(port), "")
return
}
func setupHTTPClient(httpEndpoint, httpsEndpoint string) (client *http.Client, err error) {
_, fileName, _, _ := runtime.Caller(0)
var repoRoot string
if repoRoot, err = filepath.Abs(filepath.Join(filepath.Dir(fileName), "..", "..", "..", "..", "..")); err != nil {
return
}
var demoCABytes []byte
if demoCABytes, err = ioutil.ReadFile(filepath.Join(repoRoot, "assets", "demoCA", "ca.pem")); err != nil {
return
}
rootCaPool := x509.NewCertPool()
if !rootCaPool.AppendCertsFromPEM(demoCABytes) {
err = errors.New("failed to add CA key")
return
}
client = &http.Client{
Transport: &http.Transport{
Proxy: func(req *http.Request) (*url.URL, error) {
switch req.URL.Scheme {
case "http":
return url.Parse(httpEndpoint)
case "https":
return url.Parse(httpsEndpoint)
default:
return nil, errors.New("unknown scheme")
}
},
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSClientConfig: &tls.Config{
RootCAs: rootCaPool,
},
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
return
}

View file

@ -3,8 +3,8 @@ package proxy
import ( import (
"crypto/tls" "crypto/tls"
"net/http" "net/http"
"net/url"
"github.com/jinzhu/copier"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http" imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http"
"gitlab.com/inetmock/inetmock/pkg/audit" "gitlab.com/inetmock/inetmock/pkg/audit"
@ -13,6 +13,23 @@ import (
"gopkg.in/elazarl/goproxy.v1" "gopkg.in/elazarl/goproxy.v1"
) )
type proxyHttpsHandler struct {
options httpProxyOptions
tlsConfig *tls.Config
emitter audit.Emitter
}
func (p *proxyHttpsHandler) HandleConnect(_ string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
p.emitter.Emit(imHttp.EventFromRequest(ctx.Req, audit.AppProtocol_HTTP_PROXY))
return &goproxy.ConnectAction{
Action: goproxy.ConnectAccept,
TLSConfig: func(host string, ctx *goproxy.ProxyCtx) (*tls.Config, error) {
return p.tlsConfig, nil
},
}, p.options.Target.host()
}
type proxyHttpHandler struct { type proxyHttpHandler struct {
handlerName string handlerName string
options httpProxyOptions options httpProxyOptions
@ -20,38 +37,19 @@ type proxyHttpHandler struct {
emitter audit.Emitter emitter audit.Emitter
} }
type proxyHttpsHandler struct {
handlerName string
tlsConfig *tls.Config
logger logging.Logger
emitter audit.Emitter
}
func (p *proxyHttpsHandler) HandleConnect(req string, _ *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
totalHttpsRequestCounter.WithLabelValues(p.handlerName).Inc()
p.logger.Info(
"Intercepting HTTPS proxy request",
zap.String("request", req),
)
return &goproxy.ConnectAction{
Action: goproxy.ConnectMitm,
TLSConfig: func(host string, ctx *goproxy.ProxyCtx) (*tls.Config, error) {
return p.tlsConfig, nil
},
}, ""
}
func (p *proxyHttpHandler) Handle(req *http.Request, ctx *goproxy.ProxyCtx) (retReq *http.Request, resp *http.Response) { func (p *proxyHttpHandler) Handle(req *http.Request, ctx *goproxy.ProxyCtx) (retReq *http.Request, resp *http.Response) {
timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(p.handlerName)) timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(p.handlerName))
defer timer.ObserveDuration() defer timer.ObserveDuration()
totalRequestCounter.WithLabelValues(p.handlerName).Inc()
retReq = req retReq = req
p.emitter.Emit(imHttp.EventFromRequest(req, audit.AppProtocol_HTTP_PROXY)) p.emitter.Emit(imHttp.EventFromRequest(req, audit.AppProtocol_HTTP_PROXY))
var err error var err error
if resp, err = ctx.RoundTrip(p.redirectHTTPRequest(req)); err != nil { var redirectReq *http.Request
if redirectReq, err = redirectHTTPRequest(p.options.Target.host(), req); err != nil {
return req, nil
}
if resp, err = ctx.RoundTrip(redirectReq); err != nil {
p.logger.Error( p.logger.Error(
"error while doing roundtrip", "error while doing roundtrip",
zap.Error(err), zap.Error(err),
@ -62,35 +60,11 @@ func (p *proxyHttpHandler) Handle(req *http.Request, ctx *goproxy.ProxyCtx) (ret
return return
} }
func (p proxyHttpHandler) redirectHTTPRequest(originalRequest *http.Request) (redirectReq *http.Request) { func redirectHTTPRequest(targetHost string, originalRequest *http.Request) (redirectReq *http.Request, err error) {
redirectReq = &http.Request{ redirectReq = new(http.Request)
Method: originalRequest.Method, if err = copier.Copy(redirectReq, originalRequest); err != nil {
URL: &url.URL{ return
Host: p.options.Target.host(),
Path: originalRequest.URL.Path,
ForceQuery: originalRequest.URL.ForceQuery,
Fragment: originalRequest.URL.Fragment,
Opaque: originalRequest.URL.Opaque,
RawPath: originalRequest.URL.RawPath,
RawQuery: originalRequest.URL.RawQuery,
User: originalRequest.URL.User,
},
Proto: originalRequest.Proto,
ProtoMajor: originalRequest.ProtoMajor,
ProtoMinor: originalRequest.ProtoMinor,
Header: originalRequest.Header,
Body: originalRequest.Body,
GetBody: originalRequest.GetBody,
ContentLength: originalRequest.ContentLength,
TransferEncoding: originalRequest.TransferEncoding,
Close: false,
Host: originalRequest.Host,
Form: originalRequest.Form,
PostForm: originalRequest.PostForm,
MultipartForm: originalRequest.MultipartForm,
Trailer: originalRequest.Trailer,
} }
redirectReq = redirectReq.WithContext(originalRequest.Context()) originalRequest.URL.Host = targetHost
return return
} }

View file

@ -2,7 +2,7 @@ package proxy
import ( import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/pkg/api" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/metrics" "gitlab.com/inetmock/inetmock/pkg/metrics"
"go.uber.org/zap" "go.uber.org/zap"
@ -11,12 +11,10 @@ import (
var ( var (
handlerNameLblName = "handler_name" handlerNameLblName = "handler_name"
totalRequestCounter *prometheus.CounterVec
totalHttpsRequestCounter *prometheus.CounterVec
requestDurationHistogram *prometheus.HistogramVec requestDurationHistogram *prometheus.HistogramVec
) )
func AddHTTPProxy(registry api.HandlerRegistry) (err error) { func AddHTTPProxy(registry endpoint.HandlerRegistry) (err error) {
var logger logging.Logger var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil { if logger, err = logging.CreateLogger(); err != nil {
return return
@ -25,19 +23,11 @@ func AddHTTPProxy(registry api.HandlerRegistry) (err error) {
zap.String("protocol_handler", name), zap.String("protocol_handler", name),
) )
if totalRequestCounter, err = metrics.Counter(name, "total_requests", "", handlerNameLblName); err != nil {
return
}
if requestDurationHistogram, err = metrics.Histogram(name, "request_duration", "", nil, handlerNameLblName); err != nil { if requestDurationHistogram, err = metrics.Histogram(name, "request_duration", "", nil, handlerNameLblName); err != nil {
return return
} }
if totalHttpsRequestCounter, err = metrics.Counter(name, "total_https_requests", "", handlerNameLblName); err != nil { registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return
}
registry.RegisterHandler(name, func() api.ProtocolHandler {
return &httpProxy{ return &httpProxy{
logger: logger, logger: logger,
proxy: goproxy.NewProxyHttpServer(), proxy: goproxy.NewProxyHttpServer(),

View file

@ -1,13 +1,11 @@
package metrics package metrics
import ( import (
"context"
"errors" "errors"
"net/http" "net/http"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"gitlab.com/inetmock/inetmock/pkg/api" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -21,35 +19,37 @@ type metricsExporter struct {
server *http.Server server *http.Server
} }
func (m *metricsExporter) Start(_ api.PluginContext, config config.HandlerConfig) (err error) { func (m *metricsExporter) Start(lifecycle endpoint.Lifecycle) (err error) {
exporterOptions := metricsExporterOptions{} var exporterOptions metricsExporterOptions
if err = config.Options.Unmarshal(&exporterOptions); err != nil { if err = lifecycle.UnmarshalOptions(&exporterOptions); err != nil {
return return
} }
m.logger = m.logger.With( m.logger = m.logger.With(
zap.String("handler_name", config.HandlerName), zap.String("handler_name", lifecycle.Name()),
zap.String("address", config.ListenAddr()), zap.String("address", lifecycle.Uplink().Addr().String()),
) )
mux := http.NewServeMux() mux := http.NewServeMux()
mux.Handle(exporterOptions.Route, promhttp.Handler()) mux.Handle(exporterOptions.Route, promhttp.Handler())
m.server = &http.Server{ m.server = &http.Server{
Addr: config.ListenAddr(),
Handler: mux, Handler: mux,
} }
go func() { go func() {
if err := m.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { if err := m.server.Serve(lifecycle.Uplink().Listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
m.logger.Error( m.logger.Error(
"Error occurred while serving metrics", "Error occurred while serving metrics",
zap.Error(err), zap.Error(err),
) )
} }
}() }()
go func() {
<-lifecycle.Context().Done()
if err := m.server.Close(); err != nil && !errors.Is(err, http.ErrServerClosed) {
m.logger.Error("failed to stop metrics server", zap.Error(err))
}
}()
return return
} }
func (m *metricsExporter) Shutdown(ctx context.Context) error {
return m.server.Shutdown(ctx)
}

View file

@ -1,12 +1,12 @@
package metrics package metrics
import ( import (
"gitlab.com/inetmock/inetmock/pkg/api" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )
func AddMetricsExporter(registry api.HandlerRegistry) (err error) { func AddMetricsExporter(registry endpoint.HandlerRegistry) (err error) {
var logger logging.Logger var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil { if logger, err = logging.CreateLogger(); err != nil {
return return
@ -15,7 +15,7 @@ func AddMetricsExporter(registry api.HandlerRegistry) (err error) {
zap.String("protocol_handler", name), zap.String("protocol_handler", name),
) )
registry.RegisterHandler(name, func() api.ProtocolHandler { registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &metricsExporter{ return &metricsExporter{
logger: logger, logger: logger,
} }

View file

@ -3,14 +3,13 @@ package interceptor
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt"
"net" "net"
"sync" "sync"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/pkg/api" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -30,36 +29,28 @@ type tlsInterceptor struct {
connectionsMutex *sync.Mutex connectionsMutex *sync.Mutex
} }
func (t *tlsInterceptor) Start(ctx api.PluginContext, config config.HandlerConfig) (err error) { func (t *tlsInterceptor) Start(ctx endpoint.Lifecycle) (err error) {
t.name = config.HandlerName t.name = ctx.Name()
if err = config.Options.Unmarshal(&t.options); err != nil { if err = ctx.UnmarshalOptions(&t.options); err != nil {
return return
} }
t.logger = t.logger.With( t.logger = t.logger.With(
zap.String("handler_name", config.HandlerName), zap.String("handler_name", ctx.Name()),
zap.String("address", config.ListenAddr()), zap.String("address", ctx.Uplink().Addr().String()),
zap.String("Target", t.options.Target.address()), zap.String("Target", t.options.Target.address()),
) )
if t.listener, err = tls.Listen("tcp", config.ListenAddr(), ctx.CertStore().TLSConfig()); err != nil { t.listener = tls.NewListener(ctx.Uplink().Listener, ctx.CertStore().TLSConfig())
t.logger.Fatal(
"failed to create tls listener",
zap.Error(err),
)
err = fmt.Errorf(
"failed to create tls listener: %w",
err,
)
return
}
go t.startListener() go t.startListener()
go t.shutdownOnContextDone(ctx.Context())
return return
} }
func (t *tlsInterceptor) Shutdown(ctx context.Context) (err error) { func (t *tlsInterceptor) shutdownOnContextDone(ctx context.Context) {
<-ctx.Done()
t.logger.Info("Shutting down TLS interceptor") t.logger.Info("Shutting down TLS interceptor")
t.shutdownRequested = true t.shutdownRequested = true
done := make(chan struct{}) done := make(chan struct{})
@ -71,17 +62,13 @@ func (t *tlsInterceptor) Shutdown(ctx context.Context) (err error) {
select { select {
case <-done: case <-done:
return return
case <-ctx.Done(): case <-time.After(100 * time.Millisecond):
for _, proxyConn := range t.currentConnections { for _, proxyConn := range t.currentConnections {
if err = proxyConn.Close(); err != nil { if err := proxyConn.Close(); err != nil {
t.logger.Error( t.logger.Error(
"error while closing remaining proxy connections", "error while closing remaining proxy connections",
zap.Error(err), zap.Error(err),
) )
err = fmt.Errorf(
"error while closing remaining proxy connections: %w",
err,
)
} }
} }
return return

View file

@ -5,7 +5,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/pkg/api" "gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/metrics" "gitlab.com/inetmock/inetmock/pkg/metrics"
"go.uber.org/zap" "go.uber.org/zap"
@ -18,7 +18,7 @@ var (
requestDurationHistogram *prometheus.HistogramVec requestDurationHistogram *prometheus.HistogramVec
) )
func AddTLSInterceptor(registry api.HandlerRegistry) (err error) { func AddTLSInterceptor(registry endpoint.HandlerRegistry) (err error) {
var logger logging.Logger var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil { if logger, err = logging.CreateLogger(); err != nil {
panic(err) panic(err)
@ -37,7 +37,7 @@ func AddTLSInterceptor(registry api.HandlerRegistry) (err error) {
} }
registry.RegisterHandler(name, func() api.ProtocolHandler { registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &tlsInterceptor{ return &tlsInterceptor{
logger: logger, logger: logger,
currentConnectionsCount: new(sync.WaitGroup), currentConnectionsCount: new(sync.WaitGroup),

View file

@ -0,0 +1,69 @@
package endpoint
import (
"context"
"github.com/mitchellh/mapstructure"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
type endpointLifecycle struct {
endpointName string
ctx context.Context
logger logging.Logger
certStore cert.Store
emitter audit.Emitter
uplink Uplink
tls bool
opts map[string]interface{}
}
func NewEndpointLifecycleFromContext(
endpointName string,
ctx context.Context,
logger logging.Logger,
certStore cert.Store,
emitter audit.Emitter,
uplink Uplink,
opts map[string]interface{},
) Lifecycle {
return &endpointLifecycle{
endpointName: endpointName,
ctx: ctx,
logger: logger,
certStore: certStore,
emitter: emitter,
uplink: uplink,
opts: opts,
}
}
func (e endpointLifecycle) Name() string {
return e.endpointName
}
func (e endpointLifecycle) Uplink() Uplink {
return e.uplink
}
func (e endpointLifecycle) Logger() logging.Logger {
return e.logger
}
func (e endpointLifecycle) CertStore() cert.Store {
return e.certStore
}
func (e endpointLifecycle) Audit() audit.Emitter {
return e.emitter
}
func (e endpointLifecycle) Context() context.Context {
return e.ctx
}
func (e endpointLifecycle) UnmarshalOptions(cfg interface{}) error {
return mapstructure.Decode(e.opts, cfg)
}

View file

@ -0,0 +1,143 @@
//go:generate go-enum -f $GOFILE --lower --marshal --names
package endpoint
import (
"crypto/tls"
"errors"
"fmt"
"net"
"sort"
"strings"
"github.com/soheilhy/cmux"
)
var (
ErrUDPMultiplexer = errors.New("UDP listeners don't support multiplexing")
ErrMultiplexingNotSupported = errors.New("not all handlers do support multiplexing")
)
/* ENUM(
UDP,
TCP
)
*/
type NetProto int
type HandlerReference string
func (h HandlerReference) ToLower() HandlerReference {
return HandlerReference(strings.ToLower(string(h)))
}
type ListenerSpec struct {
Name string
Protocol string
Address string `mapstructure:"listenAddress"`
Port uint16
Endpoints map[string]Spec
Uplink *Uplink `mapstructure:"-"`
}
type Spec struct {
HandlerRef HandlerReference `mapstructure:"handler"`
TLS bool
Handler ProtocolHandler `mapstructure:"-"`
Options map[string]interface{}
}
func (l *ListenerSpec) ConfigureMultiplexing(tlsConfig *tls.Config) (endpoints []Endpoint, muxes []cmux.CMux, err error) {
if l.Uplink == nil {
if err = l.setupUplink(); err != nil {
return
}
}
if len(l.Endpoints) <= 1 {
for name, s := range l.Endpoints {
endpoints = append(endpoints, Endpoint{
name: fmt.Sprintf("%s:%s", l.Name, name),
uplink: *l.Uplink,
Spec: s,
})
return
}
}
if l.Uplink.Proto == NetProtoUDP {
err = ErrUDPMultiplexer
return
}
var epNames []string
var multiplexEndpoints = make(map[string]MultiplexHandler)
for name, spec := range l.Endpoints {
epNames = append(epNames, name)
if ep, ok := spec.Handler.(MultiplexHandler); !ok {
err = fmt.Errorf("handler %s %w", spec.HandlerRef, ErrMultiplexingNotSupported)
return
} else {
multiplexEndpoints[name] = ep
}
}
sort.Strings(epNames)
plainMux := cmux.New(l.Uplink.Listener)
tlsListener := plainMux.Match(cmux.TLS())
tlsListener = tls.NewListener(tlsListener, tlsConfig)
tlsMux := cmux.New(tlsListener)
var tlsRequired = false
for _, epName := range epNames {
epSpec := l.Endpoints[epName]
var epMux = plainMux
if epSpec.TLS {
epMux = tlsMux
tlsRequired = true
}
epListener := Endpoint{
name: fmt.Sprintf("%s:%s", l.Name, epName),
uplink: Uplink{
Proto: NetProtoTCP,
Listener: epMux.Match(multiplexEndpoints[epName].Matchers()...),
},
Spec: epSpec,
}
endpoints = append(endpoints, epListener)
}
muxes = append(muxes, plainMux)
if tlsRequired {
muxes = append(muxes, tlsMux)
} else {
_ = tlsListener.Close()
}
return
}
func (l *ListenerSpec) setupUplink() (err error) {
l.Uplink = new(Uplink)
switch l.Protocol {
case "udp", "udp4", "udp6":
l.Uplink.Proto = NetProtoUDP
l.Uplink.PacketConn, err = net.ListenUDP(l.Protocol, &net.UDPAddr{
IP: net.ParseIP(l.Address),
Port: int(l.Port),
})
case "tcp", "tcp4", "tcp6":
l.Uplink.Proto = NetProtoTCP
l.Uplink.Listener, err = net.ListenTCP(l.Protocol, &net.TCPAddr{
IP: net.ParseIP(l.Address),
Port: int(l.Port),
})
default:
err = errors.New("protocol not supported")
}
return
}

View file

@ -0,0 +1,102 @@
package endpoint
import (
"context"
"errors"
"github.com/soheilhy/cmux"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
var (
ErrStartupTimeout = errors.New("endpoint did not start in time")
)
type Orchestrator interface {
RegisterListener(spec ListenerSpec) error
StartEndpoints() (errChan chan error)
}
func NewOrchestrator(appCtx context.Context, certStore cert.Store, registry HandlerRegistry, emitter audit.Emitter, logging logging.Logger) Orchestrator {
return &orchestrator{
appCtx: appCtx,
registry: registry,
logger: logging,
certStore: certStore,
emitter: emitter,
}
}
type orchestrator struct {
appCtx context.Context
registry HandlerRegistry
logger logging.Logger
certStore cert.Store
emitter audit.Emitter
endpointListeners []Endpoint
muxes []cmux.CMux
}
func (e *orchestrator) RegisterListener(spec ListenerSpec) (err error) {
for name, s := range spec.Endpoints {
if handler, registered := e.registry.HandlerForName(s.HandlerRef); registered {
s.Handler = handler
spec.Endpoints[name] = s
}
}
var endpoints []Endpoint
var muxes []cmux.CMux
if endpoints, muxes, err = spec.ConfigureMultiplexing(e.certStore.TLSConfig()); err != nil {
return
}
e.endpointListeners = append(e.endpointListeners, endpoints...)
e.muxes = append(e.muxes, muxes...)
return
}
func (e *orchestrator) StartEndpoints() (errChan chan error) {
errChan = make(chan error)
for _, epListener := range e.endpointListeners {
endpointLogger := e.logger.With(
zap.String("epListener", epListener.name),
)
endpointLogger.Info("Starting epListener")
lifecycle := NewEndpointLifecycleFromContext(
epListener.name,
e.appCtx,
e.logger.With(zap.String("epListener", epListener.name)),
e.certStore,
e.emitter,
epListener.uplink,
epListener.Options,
)
if err := epListener.Start(lifecycle); err == nil {
endpointLogger.Info("successfully started epListener")
} else {
endpointLogger.Error("error occurred during epListener startup - will be skipped for now")
}
}
e.logger.Info("Startup of all endpoints completed")
for _, mux := range e.muxes {
go func(mux cmux.CMux) {
mux.HandleError(func(err error) bool {
errChan <- err
return true
})
if err := mux.Serve(); err != nil && !errors.Is(err, cmux.ErrListenerClosed) {
errChan <- err
}
}(mux)
}
return
}

View file

@ -0,0 +1,47 @@
//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/endpoint/handler_registry.mock.go -package=endpoint_mock
package endpoint
import (
"fmt"
)
type Registration func(registry HandlerRegistry) error
type HandlerRegistry interface {
RegisterHandler(handlerRef HandlerReference, handlerProvider func() ProtocolHandler)
AvailableHandlers() []HandlerReference
HandlerForName(handlerRef HandlerReference) (ProtocolHandler, bool)
}
func NewHandlerRegistry() HandlerRegistry {
return &handlerRegistry{
handlers: make(map[HandlerReference]func() ProtocolHandler),
}
}
type handlerRegistry struct {
handlers map[HandlerReference]func() ProtocolHandler
}
func (h handlerRegistry) AvailableHandlers() (availableHandlers []HandlerReference) {
for s := range h.handlers {
availableHandlers = append(availableHandlers, s)
}
return
}
func (h *handlerRegistry) HandlerForName(handlerRef HandlerReference) (instance ProtocolHandler, ok bool) {
var provider func() ProtocolHandler
if provider, ok = h.handlers[handlerRef.ToLower()]; ok {
instance = provider()
}
return
}
func (h *handlerRegistry) RegisterHandler(handlerRef HandlerReference, handlerProvider func() ProtocolHandler) {
handlerRef = handlerRef.ToLower()
if _, exists := h.handlers[handlerRef]; exists {
panic(fmt.Sprintf("handler with name %s is already registered - there's something strange...in the neighborhood", handlerRef))
}
h.handlers[handlerRef] = handlerProvider
}

View file

@ -0,0 +1,33 @@
package endpoint
import (
"net"
"go.uber.org/multierr"
)
type Uplink struct {
Proto NetProto
Listener net.Listener
PacketConn net.PacketConn
}
func (u Uplink) Addr() net.Addr {
if u.Listener != nil {
return u.Listener.Addr()
}
if u.PacketConn != nil {
return u.PacketConn.LocalAddr()
}
return nil
}
func (u Uplink) Close() (err error) {
if u.Listener != nil {
err = multierr.Append(err, u.Listener.Close())
}
if u.PacketConn != nil {
err = multierr.Append(err, u.PacketConn.Close())
}
return
}

View file

@ -1,37 +0,0 @@
package rpc
import (
"context"
"gitlab.com/inetmock/inetmock/internal/endpoint"
)
type endpointsServer struct {
UnimplementedEndpointsServer
endpointsManager endpoint.EndpointManager
}
func (e endpointsServer) GetEndpoints(_ context.Context, _ *GetEndpointsRequest) (*GetEndpointsResponse, error) {
eps := rpcEndpointsFromEndpoints(e.endpointsManager.StartedEndpoints())
return &GetEndpointsResponse{
Endpoints: *eps,
}, nil
}
func rpcEndpointsFromEndpoints(eps []endpoint.Endpoint) *[]*Endpoint {
out := make([]*Endpoint, 0)
for _, ep := range eps {
out = append(out, rpcEndpointFromEndpoint(ep))
}
return &out
}
func rpcEndpointFromEndpoint(ep endpoint.Endpoint) *Endpoint {
return &Endpoint{
Id: ep.Id().String(),
Name: ep.Name(),
Handler: ep.Handler(),
ListenAddress: ep.Listen(),
Port: int32(ep.Port()),
}
}

View file

@ -3,6 +3,7 @@ package rpc
import ( import (
"net" "net"
"net/url" "net/url"
"os"
"time" "time"
app2 "gitlab.com/inetmock/inetmock/internal/app" app2 "gitlab.com/inetmock/inetmock/internal/app"
@ -42,13 +43,6 @@ func (i *inetmockAPI) StartServer() (err error) {
} }
i.server = grpc.NewServer() i.server = grpc.NewServer()
RegisterHandlersServer(i.server, &handlersServer{
registry: i.app.HandlerRegistry(),
})
RegisterEndpointsServer(i.server, &endpointsServer{
endpointsManager: i.app.EndpointManager(),
})
RegisterHealthServer(i.server, &healthServer{ RegisterHealthServer(i.server, &healthServer{
app: i.app, app: i.app,
}) })
@ -98,6 +92,11 @@ func (i *inetmockAPI) startServerAsync(listener net.Listener) {
func createListenerFromURL(url *url.URL) (lis net.Listener, err error) { func createListenerFromURL(url *url.URL) (lis net.Listener, err error) {
switch url.Scheme { switch url.Scheme {
case "unix": case "unix":
if _, err = os.Stat(url.Path); err == nil {
if err = os.Remove(url.Path); err != nil {
return
}
}
lis, err = net.Listen(url.Scheme, url.Path) lis, err = net.Listen(url.Scheme, url.Path)
default: default:
lis, err = net.Listen(url.Scheme, url.Host) lis, err = net.Listen(url.Scheme, url.Host)

View file

@ -1,18 +0,0 @@
package rpc
import (
"context"
"gitlab.com/inetmock/inetmock/pkg/api"
)
type handlersServer struct {
UnimplementedHandlersServer
registry api.HandlerRegistry
}
func (h *handlersServer) GetHandlers(_ context.Context, _ *GetHandlersRequest) (*GetHandlersResponse, error) {
return &GetHandlersResponse{
Handlers: h.registry.AvailableHandlers(),
}, nil
}

View file

@ -0,0 +1,64 @@
package integration
import (
"context"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/docker/go-connections/nat"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
func SetupINetMockContainer(ctx context.Context, tb testing.TB, exposedPorts ...string) (imContainer testcontainers.Container, err error) {
_, fileName, _, _ := runtime.Caller(0)
var repoRoot string
if repoRoot, err = filepath.Abs(filepath.Join(filepath.Dir(fileName), "..", "..", "..")); err != nil {
return
}
var waitStrategies []wait.Strategy
var tcpPortPresent = false
for _, port := range exposedPorts {
if strings.Contains(port, "tcp") {
tcpPortPresent = true
waitStrategies = append(waitStrategies, wait.ForListeningPort(nat.Port(port)))
}
}
if !tcpPortPresent {
exposedPorts = append(exposedPorts, "80/tcp")
waitStrategies = append(waitStrategies, wait.ForListeningPort("80/tcp"))
}
req := testcontainers.ContainerRequest{
FromDockerfile: testcontainers.FromDockerfile{
Context: repoRoot,
Dockerfile: filepath.Join("./", "testdata", "integration.dockerfile"),
},
ExposedPorts: exposedPorts,
WaitingFor: wait.ForAll(waitStrategies...),
}
imContainer, err = testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
if err != nil {
return
}
tb.Cleanup(func() {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = imContainer.Terminate(shutdownCtx)
})
return
}

View file

@ -1,22 +0,0 @@
//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/api/protocol_handler.mock.go -package=api_mock
package api
import (
"context"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/logging"
)
type PluginContext interface {
Logger() logging.Logger
CertStore() cert.Store
Audit() audit.Emitter
}
type ProtocolHandler interface {
Start(ctx PluginContext, config config.HandlerConfig) error
Shutdown(ctx context.Context) error
}

View file

@ -1,54 +0,0 @@
//go:generate mockgen -source=$GOFILE -destination=./../../internal/mock/plugins/handler_registry.mock.go -package=plugins_mock
package api
import (
"fmt"
"regexp"
"strings"
)
var (
pluginFileNamePattern = regexp.MustCompile(`[\w\-]+\.so$`)
)
type Registration func(registry HandlerRegistry) error
type HandlerRegistry interface {
RegisterHandler(handlerName string, handlerProvider func() ProtocolHandler)
AvailableHandlers() []string
HandlerForName(handlerName string) (ProtocolHandler, bool)
}
func NewHandlerRegistry() HandlerRegistry {
return &handlerRegistry{
handlers: make(map[string]func() ProtocolHandler),
}
}
type handlerRegistry struct {
handlers map[string]func() ProtocolHandler
}
func (h handlerRegistry) AvailableHandlers() (availableHandlers []string) {
for s := range h.handlers {
availableHandlers = append(availableHandlers, s)
}
return
}
func (h *handlerRegistry) HandlerForName(handlerName string) (instance ProtocolHandler, ok bool) {
handlerName = strings.ToLower(handlerName)
var provider func() ProtocolHandler
if provider, ok = h.handlers[handlerName]; ok {
instance = provider()
}
return
}
func (h *handlerRegistry) RegisterHandler(handlerName string, handlerProvider func() ProtocolHandler) {
handlerName = strings.ToLower(handlerName)
if _, exists := h.handlers[handlerName]; exists {
panic(fmt.Sprintf("handler with name %s is already registered - there's something strange...in the neighborhood", handlerName))
}
h.handlers[handlerName] = handlerProvider
}

View file

@ -1,59 +0,0 @@
package api
import (
"reflect"
"testing"
)
func Test_handlerRegistry_HandlerForName(t *testing.T) {
type fields struct {
handlers map[string]func() ProtocolHandler
}
type args struct {
handlerName string
}
tests := []struct {
name string
fields fields
args args
wantInstance ProtocolHandler
wantOk bool
}{
{
name: "No instance if nothing is registered",
fields: fields{},
args: args{},
wantInstance: nil,
wantOk: false,
},
{
name: "Nil instance from pseudo factory",
fields: fields{
handlers: map[string]func() ProtocolHandler{
"pseudo": func() ProtocolHandler {
return nil
},
},
},
args: args{
handlerName: "pseudo",
},
wantInstance: nil,
wantOk: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &handlerRegistry{
handlers: tt.fields.handlers,
}
gotInstance, gotOk := h.HandlerForName(tt.args.handlerName)
if !reflect.DeepEqual(gotInstance, tt.wantInstance) {
t.Errorf("HandlerForName() gotInstance = %v, want %v", gotInstance, tt.wantInstance)
}
if gotOk != tt.wantOk {
t.Errorf("HandlerForName() gotOk = %v, want %v", gotOk, tt.wantOk)
}
})
}
}

View file

@ -29,7 +29,7 @@ type Event struct {
TLS *TLSDetails TLS *TLSDetails
} }
func (e *Event) ProtoMessage() *EventEntity { func (e Event) ProtoMessage() *EventEntity {
var tlsDetails *TLSDetailsEntity = nil var tlsDetails *TLSDetailsEntity = nil
if e.TLS != nil { if e.TLS != nil {
tlsDetails = e.TLS.ProtoMessage() tlsDetails = e.TLS.ProtoMessage()

View file

@ -12,7 +12,6 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
certmock "gitlab.com/inetmock/inetmock/internal/mock/cert" certmock "gitlab.com/inetmock/inetmock/internal/mock/cert"
"gitlab.com/inetmock/inetmock/pkg/config"
) )
const ( const (
@ -286,13 +285,13 @@ func Test_fileSystemCache_Put(t *testing.T) {
} }
func setupCertGen() Generator { func setupCertGen() Generator {
return NewDefaultGenerator(config.CertOptions{ return NewDefaultGenerator(CertOptions{
Validity: config.ValidityByPurpose{ Validity: ValidityByPurpose{
Server: config.ValidityDuration{ Server: ValidityDuration{
NotBeforeRelative: serverRelativeValidity, NotBeforeRelative: serverRelativeValidity,
NotAfterRelative: serverRelativeValidity, NotAfterRelative: serverRelativeValidity,
}, },
CA: config.ValidityDuration{ CA: ValidityDuration{
NotBeforeRelative: caRelativeValidity, NotBeforeRelative: caRelativeValidity,
NotAfterRelative: caRelativeValidity, NotAfterRelative: caRelativeValidity,
}, },

View file

@ -1,4 +1,4 @@
package config package cert
import ( import (
"crypto/tls" "crypto/tls"

View file

@ -1,13 +1,14 @@
package cert package cert
const ( const (
defaultServerValidityDuration = "168h" CurveTypeP224 CurveType = "P224"
defaultCAValidityDuration = "17520h" CurveTypeP256 CurveType = "P256"
CurveTypeP384 CurveType = "P384"
CurveTypeP521 CurveType = "P521"
CurveTypeED25519 CurveType = "ED25519"
certCachePathConfigKey = "tls.certCachePath" TLSVersionSSL3 TLSVersion = "SSL3"
ecdsaCurveConfigKey = "tls.ecdsaCurve" TLSVersionTLS10 TLSVersion = "TLS10"
caCertValidityNotBeforeKey = "tls.validity.ca.notBeforeRelative" TLSVersionTLS11 TLSVersion = "TLS11"
caCertValidityNotAfterKey = "tls.validity.ca.notAfterRelative" TLSVersionTLS12 TLSVersion = "TLS12"
serverCertValidityNotBeforeKey = "tls.validity.server.notBeforeRelative"
serverCertValidityNotAfterKey = "tls.validity.server.notAfterRelative"
) )

View file

@ -1,44 +1,20 @@
package cert package cert
import ( import (
"reflect" "github.com/imdario/mergo"
"gitlab.com/inetmock/inetmock/pkg/defaulting"
) )
var ( var (
certOptionsDefaulter defaulting.Defaulter = func(instance interface{}) { defaultOptions = &GenerationOptions{
switch o := instance.(type) { Country: []string{"US"},
case *GenerationOptions: Locality: []string{"San Francisco"},
Organization: []string{"INetMock"},
if len(o.Country) < 1 { StreetAddress: []string{"Golden Gate Bridge"},
o.Country = []string{"US"} PostalCode: []string{"94016"},
} Province: []string{""},
if len(o.Locality) < 1 {
o.Locality = []string{"San Francisco"}
}
if len(o.Organization) < 1 {
o.Organization = []string{"INetMock"}
}
if len(o.StreetAddress) < 1 {
o.StreetAddress = []string{"Golden Gate Bridge"}
}
if len(o.PostalCode) < 1 {
o.PostalCode = []string{"94016"}
}
if len(o.Province) < 1 {
o.Province = []string{""}
}
}
} }
) )
func init() { func applyDefaultGenerationOptions(opts *GenerationOptions) error {
certOptionsType := reflect.TypeOf(GenerationOptions{}) return mergo.Merge(opts, defaultOptions)
defaulters.Register(certOptionsType, certOptionsDefaulter)
} }

View file

@ -6,13 +6,14 @@ import (
) )
func Test_certOptionsDefaulter(t *testing.T) { func Test_certOptionsDefaulter(t *testing.T) {
tests := []struct { type testCase struct {
name string name string
arg GenerationOptions arg GenerationOptions
expected GenerationOptions expected GenerationOptions
}{ }
tests := []testCase{
{ {
name: "", name: "Empty options",
arg: GenerationOptions{ arg: GenerationOptions{
CommonName: "CA", CommonName: "CA",
}, },
@ -26,14 +27,50 @@ func Test_certOptionsDefaulter(t *testing.T) {
Province: []string{""}, Province: []string{""},
}, },
}, },
{
name: "Options with country set",
arg: GenerationOptions{
CommonName: "CA",
Country: []string{"DE"},
},
expected: GenerationOptions{
CommonName: "CA",
Country: []string{"DE"},
Locality: []string{"San Francisco"},
Organization: []string{"INetMock"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
Province: []string{""},
},
},
{
name: "Options with organization set set",
arg: GenerationOptions{
CommonName: "CA",
Organization: []string{"inetmock"},
},
expected: GenerationOptions{
CommonName: "CA",
Country: []string{"US"},
Locality: []string{"San Francisco"},
Organization: []string{"inetmock"},
StreetAddress: []string{"Golden Gate Bridge"},
PostalCode: []string{"94016"},
Province: []string{""},
},
},
} }
scenario := func(tt testCase) func(t *testing.T) {
for _, tt := range tests { return func(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { if err := applyDefaultGenerationOptions(&tt.arg); err != nil {
certOptionsDefaulter(&tt.arg) t.Errorf("applyDefaultGenerationOptions() error = %v", err)
}
if !reflect.DeepEqual(tt.expected, tt.arg) { if !reflect.DeepEqual(tt.expected, tt.arg) {
t.Errorf("Apply defaulter expected=%v got=%v", tt.expected, tt.arg) t.Errorf("Apply defaulter expected=%v got=%v", tt.expected, tt.arg)
} }
}) }
}
for _, tt := range tests {
t.Run(tt.name, scenario(tt))
} }
} }

View file

@ -10,13 +10,6 @@ import (
"encoding/pem" "encoding/pem"
"math/big" "math/big"
"net" "net"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/defaulting"
)
var (
defaulters = defaulting.New()
) )
type GenerationOptions struct { type GenerationOptions struct {
@ -37,11 +30,11 @@ type Generator interface {
ServerCert(options GenerationOptions, ca *tls.Certificate) (*tls.Certificate, error) ServerCert(options GenerationOptions, ca *tls.Certificate) (*tls.Certificate, error)
} }
func NewDefaultGenerator(options config.CertOptions) Generator { func NewDefaultGenerator(options CertOptions) Generator {
return NewGenerator(options, NewTimeSource(), defaultKeyProvider(options)) return NewGenerator(options, NewTimeSource(), defaultKeyProvider(options))
} }
func NewGenerator(options config.CertOptions, source TimeSource, provider KeyProvider) Generator { func NewGenerator(options CertOptions, source TimeSource, provider KeyProvider) Generator {
return &generator{ return &generator{
options: options, options: options,
provider: provider, provider: provider,
@ -50,7 +43,7 @@ func NewGenerator(options config.CertOptions, source TimeSource, provider KeyPro
} }
type generator struct { type generator struct {
options config.CertOptions options CertOptions
provider KeyProvider provider KeyProvider
timeSource TimeSource timeSource TimeSource
outDir string outDir string
@ -65,7 +58,7 @@ func (g *generator) privateKey() (key interface{}, err error) {
} }
func (g *generator) ServerCert(options GenerationOptions, ca *tls.Certificate) (cert *tls.Certificate, err error) { func (g *generator) ServerCert(options GenerationOptions, ca *tls.Certificate) (cert *tls.Certificate, err error) {
defaulters.Apply(&options) applyDefaultGenerationOptions(&options)
var serialNumber *big.Int var serialNumber *big.Int
if serialNumber, err = generateSerialNumber(); err != nil { if serialNumber, err = generateSerialNumber(); err != nil {
return return
@ -116,7 +109,7 @@ func (g *generator) ServerCert(options GenerationOptions, ca *tls.Certificate) (
} }
func (g generator) CACert(options GenerationOptions) (crt *tls.Certificate, err error) { func (g generator) CACert(options GenerationOptions) (crt *tls.Certificate, err error) {
defaulters.Apply(&options) applyDefaultGenerationOptions(&options)
var privateKey interface{} var privateKey interface{}
var serialNumber *big.Int var serialNumber *big.Int

View file

@ -1,16 +1,10 @@
package cert package cert
import ( /*func init() {
"os"
"gitlab.com/inetmock/inetmock/pkg/config"
)
func init() {
config.AddDefaultValue(certCachePathConfigKey, os.TempDir()) config.AddDefaultValue(certCachePathConfigKey, os.TempDir())
config.AddDefaultValue(ecdsaCurveConfigKey, string(config.CurveTypeED25519)) config.AddDefaultValue(ecdsaCurveConfigKey, string(CurveTypeED25519))
config.AddDefaultValue(caCertValidityNotBeforeKey, defaultCAValidityDuration) config.AddDefaultValue(caCertValidityNotBeforeKey, defaultCAValidityDuration)
config.AddDefaultValue(caCertValidityNotAfterKey, defaultCAValidityDuration) config.AddDefaultValue(caCertValidityNotAfterKey, defaultCAValidityDuration)
config.AddDefaultValue(serverCertValidityNotBeforeKey, defaultServerValidityDuration) config.AddDefaultValue(serverCertValidityNotBeforeKey, defaultServerValidityDuration)
config.AddDefaultValue(serverCertValidityNotAfterKey, defaultServerValidityDuration) config.AddDefaultValue(serverCertValidityNotAfterKey, defaultServerValidityDuration)
} }*/

View file

@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/spf13/viper" "github.com/spf13/viper"
"gitlab.com/inetmock/inetmock/pkg/config"
) )
func readViper(cfg string) *viper.Viper { func readViper(cfg string) *viper.Viper {
@ -26,7 +25,7 @@ func Test_loadFromConfig(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want config.CertOptions want CertOptions
wantErr bool wantErr bool
}{ }{
{ {
@ -49,19 +48,19 @@ tls:
certCachePath: /tmp/inetmock/ certCachePath: /tmp/inetmock/
`), `),
}, },
want: config.CertOptions{ want: CertOptions{
RootCACert: config.File{ RootCACert: File{
PublicKeyPath: "./ca.pem", PublicKeyPath: "./ca.pem",
PrivateKeyPath: "./ca.key", PrivateKeyPath: "./ca.key",
}, },
CertCachePath: "/tmp/inetmock/", CertCachePath: "/tmp/inetmock/",
Curve: config.CurveTypeP256, Curve: CurveTypeP256,
Validity: config.ValidityByPurpose{ Validity: ValidityByPurpose{
CA: config.ValidityDuration{ CA: ValidityDuration{
NotBeforeRelative: 17520 * time.Hour, NotBeforeRelative: 17520 * time.Hour,
NotAfterRelative: 17520 * time.Hour, NotAfterRelative: 17520 * time.Hour,
}, },
Server: config.ValidityDuration{ Server: ValidityDuration{
NotBeforeRelative: 168 * time.Hour, NotBeforeRelative: 168 * time.Hour,
NotAfterRelative: 168 * time.Hour, NotAfterRelative: 168 * time.Hour,
}, },
@ -77,7 +76,7 @@ tls:
privateKey: ./ca.key privateKey: ./ca.key
`), `),
}, },
want: config.CertOptions{}, want: CertOptions{},
wantErr: true, wantErr: true,
}, },
{ {
@ -89,7 +88,7 @@ tls:
publicKey: ./ca.pem publicKey: ./ca.pem
`), `),
}, },
want: config.CertOptions{}, want: CertOptions{},
wantErr: true, wantErr: true,
}, },
{ {
@ -102,19 +101,19 @@ tls:
privateKey: ./ca.key privateKey: ./ca.key
`), `),
}, },
want: config.CertOptions{ want: CertOptions{
RootCACert: config.File{ RootCACert: File{
PublicKeyPath: "./ca.pem", PublicKeyPath: "./ca.pem",
PrivateKeyPath: "./ca.key", PrivateKeyPath: "./ca.key",
}, },
CertCachePath: os.TempDir(), CertCachePath: os.TempDir(),
Curve: config.CurveTypeED25519, Curve: CurveTypeED25519,
Validity: config.ValidityByPurpose{ Validity: ValidityByPurpose{
CA: config.ValidityDuration{ CA: ValidityDuration{
NotBeforeRelative: 17520 * time.Hour, NotBeforeRelative: 17520 * time.Hour,
NotAfterRelative: 17520 * time.Hour, NotAfterRelative: 17520 * time.Hour,
}, },
Server: config.ValidityDuration{ Server: ValidityDuration{
NotBeforeRelative: 168 * time.Hour, NotBeforeRelative: 168 * time.Hour,
NotAfterRelative: 168 * time.Hour, NotAfterRelative: 168 * time.Hour,
}, },

View file

@ -8,7 +8,6 @@ import (
"crypto/x509" "crypto/x509"
"net" "net"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/logging" "gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -18,7 +17,7 @@ const (
) )
var ( var (
defaultKeyProvider = func(options config.CertOptions) func() (key interface{}, err error) { defaultKeyProvider = func(options CertOptions) func() (key interface{}, err error) {
return func() (key interface{}, err error) { return func() (key interface{}, err error) {
return privateKeyForCurve(options) return privateKeyForCurve(options)
} }
@ -34,20 +33,20 @@ type Store interface {
} }
func NewDefaultStore( func NewDefaultStore(
config config.Config, options CertOptions,
logger logging.Logger, logger logging.Logger,
) (Store, error) { ) (Store, error) {
timeSource := NewTimeSource() timeSource := NewTimeSource()
return NewStore( return NewStore(
config.TLSConfig(), options,
NewFileSystemCache(config.TLSConfig().CertCachePath, timeSource), NewFileSystemCache(options.CertCachePath, timeSource),
NewDefaultGenerator(config.TLSConfig()), NewDefaultGenerator(options),
logger, logger,
) )
} }
func NewStore( func NewStore(
options config.CertOptions, options CertOptions,
cache Cache, cache Cache,
generator Generator, generator Generator,
logger logging.Logger, logger logging.Logger,
@ -67,7 +66,7 @@ func NewStore(
} }
type store struct { type store struct {
options config.CertOptions options CertOptions
caCert *tls.Certificate caCert *tls.Certificate
cache Cache cache Cache
timeSource TimeSource timeSource TimeSource
@ -147,15 +146,15 @@ func (s *store) GetCertificate(serverName string, ip string) (cert *tls.Certific
return return
} }
func privateKeyForCurve(options config.CertOptions) (privateKey interface{}, err error) { func privateKeyForCurve(options CertOptions) (privateKey interface{}, err error) {
switch options.Curve { switch options.Curve {
case config.CurveTypeP224: case CurveTypeP224:
privateKey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) privateKey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
case config.CurveTypeP256: case CurveTypeP256:
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case config.CurveTypeP384: case CurveTypeP384:
privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case config.CurveTypeP521: case CurveTypeP521:
privateKey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) privateKey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default: default:
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

View file

@ -1,68 +0,0 @@
package config
import (
"testing"
"github.com/spf13/pflag"
)
func Test_config_ReadConfig(t *testing.T) {
type args struct {
flags *pflag.FlagSet
config string
}
tests := []struct {
name string
args args
matcher func(Config) bool
wantErr bool
}{
{
name: "Test endpoints config",
args: args{
flags: pflag.NewFlagSet("", pflag.ContinueOnError),
config: `
endpoints:
plainHttp:
handler: http_mock
listenAddress: 0.0.0.0
ports:
- 80
- 8080
options: {}
proxy:
handler: http_proxy
listenAddress: 0.0.0.0
ports:
- 3128
options:
target:
ipAddress: 127.0.0.1
port: 80
`,
},
matcher: func(c Config) bool {
if len(c.EndpointConfigs()) < 1 {
t.Error("Expected EndpointConfigs to be set but is empty")
return false
}
return true
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := CreateConfig(tt.args.flags)
if err := cfg.ReadConfigString(tt.args.config, "yaml"); (err != nil) != tt.wantErr {
t.Errorf("ReadConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.matcher(cfg) {
t.Error("matcher error")
}
})
}
}

View file

@ -1,17 +0,0 @@
package config
const (
EndpointsKey = "endpoints"
OptionsKey = "options"
CurveTypeP224 CurveType = "P224"
CurveTypeP256 CurveType = "P256"
CurveTypeP384 CurveType = "P384"
CurveTypeP521 CurveType = "P521"
CurveTypeED25519 CurveType = "ED25519"
TLSVersionSSL3 TLSVersion = "SSL3"
TLSVersionTLS10 TLSVersion = "TLS10"
TLSVersionTLS11 TLSVersion = "TLS11"
TLSVersionTLS12 TLSVersion = "TLS12"
)

View file

@ -1,18 +0,0 @@
package config
import (
"fmt"
"github.com/spf13/viper"
)
type HandlerConfig struct {
HandlerName string
Port uint16
ListenAddress string
Options *viper.Viper
}
func (h HandlerConfig) ListenAddr() string {
return fmt.Sprintf("%s:%d", h.ListenAddress, h.Port)
}

View file

@ -1,168 +0,0 @@
package config
import (
"reflect"
"testing"
"github.com/spf13/viper"
)
func Test_handlerConfig_HandlerName(t *testing.T) {
type fields struct {
handlerName string
port uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "Get empty Handler for uninitialized struct",
fields: fields{},
want: "",
},
{
name: "Get expected Handler for initialized struct",
fields: fields{
handlerName: "sampleHandler",
},
want: "sampleHandler",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := HandlerConfig{
HandlerName: tt.fields.handlerName,
Port: tt.fields.port,
ListenAddress: tt.fields.listenAddress,
Options: tt.fields.options,
}
if got := h.HandlerName; got != tt.want {
t.Errorf("Handler() = %v, want %v", got, tt.want)
}
})
}
}
func Test_handlerConfig_ListenAddress(t *testing.T) {
type fields struct {
handlerName string
port uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "Get empty ListenAddress for uninitialized struct",
fields: fields{},
want: "",
},
{
name: "Get expected ListenAddress for initialized struct",
fields: fields{
listenAddress: "0.0.0.0",
},
want: "0.0.0.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := HandlerConfig{
HandlerName: tt.fields.handlerName,
Port: tt.fields.port,
ListenAddress: tt.fields.listenAddress,
Options: tt.fields.options,
}
if got := h.ListenAddress; got != tt.want {
t.Errorf("ListenAddress() = %v, want %v", got, tt.want)
}
})
}
}
func Test_handlerConfig_Options(t *testing.T) {
type fields struct {
handlerName string
port uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want *viper.Viper
}{
{
name: "Get nil Options for uninitialized struct",
fields: fields{},
want: nil,
},
{
name: "Get expected Options for initialized struct",
fields: fields{
options: viper.New(),
},
want: viper.New(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := HandlerConfig{
HandlerName: tt.fields.handlerName,
Port: tt.fields.port,
ListenAddress: tt.fields.listenAddress,
Options: tt.fields.options,
}
if got := h.Options; !reflect.DeepEqual(got, tt.want) {
t.Errorf("Options() = %v, want %v", got, tt.want)
}
})
}
}
func Test_handlerConfig_Port(t *testing.T) {
type fields struct {
handlerName string
port uint16
listenAddress string
options *viper.Viper
}
tests := []struct {
name string
fields fields
want uint16
}{
{
name: "Get empty Port for uninitialized struct",
fields: fields{},
want: 0,
},
{
name: "Get expected Port for initialized struct",
fields: fields{
port: 80,
},
want: 80,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := HandlerConfig{
HandlerName: tt.fields.handlerName,
Port: tt.fields.port,
ListenAddress: tt.fields.listenAddress,
Options: tt.fields.options,
}
if got := h.Port; got != tt.want {
t.Errorf("Port() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,25 +0,0 @@
package config
import (
"github.com/spf13/viper"
)
type EndpointConfig struct {
Handler string
Ports []uint16
ListenAddress string
Options *viper.Viper
}
func (m EndpointConfig) HandlerConfigs() []HandlerConfig {
configs := make([]HandlerConfig, 0)
for _, port := range m.Ports {
configs = append(configs, HandlerConfig{
HandlerName: m.Handler,
Port: port,
ListenAddress: m.ListenAddress,
Options: m.Options,
})
}
return configs
}

View file

@ -1,41 +0,0 @@
package defaulting
import (
"reflect"
)
type Defaulter func(instance interface{})
type Registry interface {
Register(t reflect.Type, defaulter ...Defaulter)
Apply(instance interface{})
}
func New() Registry {
return &registry{
defaulters: make(map[reflect.Type][]Defaulter),
}
}
type registry struct {
defaulters map[reflect.Type][]Defaulter
}
func (r *registry) Register(t reflect.Type, defaulter ...Defaulter) {
var given []Defaulter
if r, ok := r.defaulters[t]; ok {
given = r
}
given = append(given, defaulter...)
r.defaulters[t] = given
}
func (r *registry) Apply(instance interface{}) {
if defs, ok := r.defaulters[reflect.TypeOf(instance)]; ok {
for _, def := range defs {
def(instance)
}
}
}

View file

@ -1,110 +0,0 @@
package defaulting
import (
"reflect"
"testing"
)
func Test_registry_Apply(t *testing.T) {
type sample struct {
i int
}
type fields struct {
defaulters map[reflect.Type][]Defaulter
}
type args struct {
instance interface{}
}
type expect struct {
result interface{}
}
tests := []struct {
name string
fields fields
args args
expect expect
}{
{
name: "Expect setting a sample value",
fields: fields{
defaulters: map[reflect.Type][]Defaulter{
reflect.TypeOf(&sample{}): {func(instance interface{}) {
if i, ok := instance.(*sample); ok {
i.i = 42
}
}},
},
},
args: args{
instance: &sample{},
},
expect: expect{
result: &sample{
i: 42,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &registry{
defaulters: tt.fields.defaulters,
}
r.Apply(tt.args.instance)
if !reflect.DeepEqual(tt.expect.result, tt.args.instance) {
t.Errorf("Apply() expected = %v got %v", tt.args.instance, tt.expect.result)
}
})
}
}
func Test_registry_Register(t *testing.T) {
type sample struct {
}
type fields struct {
defaulters map[reflect.Type][]Defaulter
}
type args struct {
t reflect.Type
defaulter []Defaulter
}
type expect struct {
length int
}
tests := []struct {
name string
fields fields
args args
expect expect
}{
{
name: "",
fields: fields{
defaulters: make(map[reflect.Type][]Defaulter),
},
args: args{
t: reflect.TypeOf(sample{}),
defaulter: []Defaulter{func(instance interface{}) {
}},
},
expect: expect{
length: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &registry{
defaulters: tt.fields.defaulters,
}
r.Register(tt.args.t, tt.args.defaulter...)
if length := len(r.defaulters); length != tt.expect.length {
t.Errorf("len(r.defaulters) expect %d got %d", tt.expect.length, length)
}
})
}
}

View file

@ -42,7 +42,7 @@ func ParseLevel(levelString string) zap.AtomicLevel {
} }
func CreateLogger() (Logger, error) { func CreateLogger() (Logger, error) {
if zapLogger, err := loggingConfig.Build(); err != nil { if zapLogger, err := loggingConfig.Build(zap.AddCallerSkip(2)); err != nil {
return nil, err return nil, err
} else { } else {
return NewLogger(zapLogger), nil return NewLogger(zapLogger), nil
@ -55,11 +55,3 @@ func CreateTestLogger(tb testing.TB) Logger {
encoder: zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), encoder: zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
} }
} }
func MustCreateLogger() Logger {
if logger, err := CreateLogger(); err != nil {
panic(err)
} else {
return logger
}
}

View file

@ -19,17 +19,19 @@ type testLogger struct {
func (t testLogger) Named(s string) Logger { func (t testLogger) Named(s string) Logger {
return testLogger{ return testLogger{
name: s, encoder: t.encoder,
tb: t.tb, name: s,
fields: t.fields, tb: t.tb,
fields: t.fields,
} }
} }
func (t testLogger) With(fields ...zap.Field) Logger { func (t testLogger) With(fields ...zap.Field) Logger {
return &testLogger{ return &testLogger{
name: t.name, encoder: t.encoder,
fields: append(t.fields, fields...), name: t.name,
tb: t.tb, fields: append(t.fields, fields...),
tb: t.tb,
} }
} }

20
testdata/integration.dockerfile vendored Normal file
View file

@ -0,0 +1,20 @@
FROM golang:1.15-alpine as build
WORKDIR /app
COPY ./ ./
RUN go build -o inetmock ./cmd/inetmock
FROM alpine:3.13
WORKDIR /app
COPY --from=build /app/inetmock ./
COPY --from=build /app/config-container.yaml /etc/inetmock/config.yaml
COPY --from=build /app/assets/fakeFiles /var/lib/inetmock/fakeFiles
COPY --from=build /app/assets/demoCA /var/lib/inetmock/ca
RUN mkdir -p /var/run/inetmock
CMD /app/inetmock serve