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

1
.gitignore vendored
View file

@ -23,3 +23,4 @@
dist/
out/
.task/
public/

View file

@ -1,4 +1,4 @@
image: registry.gitlab.com/inetmock/ci-image
image: registry.gitlab.com/inetmock/ci-image/go
stages:
- test
@ -14,6 +14,13 @@ test:
junit: out/report.xml
cobertura: out/coverage.xml
integration-test:
stage: test
services:
- docker:dind
script:
- task integration-test
lint:
stage: test
script:
@ -42,3 +49,16 @@ release:
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- 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
![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)
[![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/).
"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
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
configuration while it has a way smaller memory footprint and far better startup and shutdown times. 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).
A second advantage is that INetMock is a complete rewrite in Go.
It has a way smaller memory footprint and far better startup and shutdown times.
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).
_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!_
_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!_
## Docs
Docs are available either in the [`docs/`](./docs/) directory or as rendered markdown book at
the [GitHub pages](https://baez90.github.io/inetmock/).
Docs are available either in the [`docs/`](./docs/) directory or as rendered markdown book at the [GitHub pages](https://baez90.github.io/inetmock/).
## Contribution/feature requests

View file

@ -6,6 +6,8 @@ vars:
IMCTL_PKG: gitlab.com/inetmock/inetmock/cmd/imctl
PROTO_FILES:
sh: find ./api/ -type f -name "*.proto" -printf "%p "
BENCHMARKS:
sh: find . -type f -name "*_bench_test.go"
env:
GOOS: linux
@ -53,6 +55,15 @@ tasks:
- gocover-cobertura < {{ .OUT_DIR }}/cov.out > {{ .OUT_DIR }}/coverage.xml
- rm -f {{ .OUT_DIR }}/cov-raw.out
integration-test:
deps:
- generate
cmds:
- |
{{ range .BENCHMARKS | splitLines -}}
go test -bench=. {{ . }}
{{ end }}
cli-cover-report:
deps:
- test
@ -99,3 +110,7 @@ tasks:
- test
cmds:
- 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() {
endpointsCmd.AddCommand(getEndpoints)
handlerCmd.AddCommand(getHandlersCmd)
healthCmd.AddCommand(generalHealthCmd, containerHealthCmd)
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) {
return initGRPCConnection()
}).

View file

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

View file

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

View file

@ -41,6 +41,19 @@ x-response-rules: &httpResponseRules
matcher: Path
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:
listen: unix:///var/run/inetmock/inetmock.sock
@ -60,38 +73,15 @@ tls:
privateKeyPath: /var/lib/inetmock/ca/ca.key
certCachePath: /var/lib/inetmock/certs
endpoints:
plainHttp:
handler: http_mock
listenAddress: 0.0.0.0
ports:
- 80
- 8080
options:
<<: *httpResponseRules
https:
handler: http_mock
listenAddress: 0.0.0.0
ports:
- 443
- 8443
options:
tls: true
<<: *httpResponseRules
proxy:
handler: http_proxy
listenAddress: 0.0.0.0
ports:
- 3128
options:
target:
ipAddress: 127.0.0.1
port: 80
listeners:
udp_53:
name: ''
protocol: udp
listenAddress: ''
port: 53
endpoints:
plainDns:
handler: dns_mock
listenAddress: 0.0.0.0
ports:
- 53
options:
rules:
- pattern: ".*\\.google\\.com"
@ -102,19 +92,75 @@ endpoints:
strategy: incremental
args:
startIP: 10.0.10.0
dnsOverTlsDowngrade:
handler: tls_interceptor
listenAddress: 0.0.0.0
ports:
- 853
tcp_80:
name: ''
protocol: tcp
listenAddress: ''
port: 80
<<: *httpHandlers
tcp_443:
name: ''
protocol: tcp
listenAddress: ''
port: 443
<<: *httpHandlers
tcp_853:
name: ''
protocol: tcp
listenAddress: ''
port: 853
endpoints:
DoT:
handler: dns_mock
tls: true
options:
rules:
- pattern: ".*\\.google\\.com"
response: 1.1.1.1
- pattern: ".*\\.reddit\\.com"
response: 2.2.2.2
fallback:
strategy: incremental
args:
startIP: 10.0.10.0
tcp_3128:
name: ''
protocol: tcp
listenAddress: ''
port: 3128
endpoints:
proxyPlain:
handler: http_proxy
options:
target:
ipAddress: 127.0.0.1
port: 53
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
listenAddress: 0.0.0.0
ports:
- 9110
options:
route: /metrics

View file

@ -41,6 +41,19 @@ x-response-rules: &httpResponseRules
matcher: Path
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:
listen: unix:///var/run/inetmock.sock
@ -60,38 +73,15 @@ tls:
privateKeyPath: ./assets/demoCA/ca.key
certCachePath: /tmp/inetmock/
endpoints:
plainHttp:
handler: http_mock
listenAddress: 0.0.0.0
ports:
- 80
- 8080
options:
<<: *httpResponseRules
https:
handler: http_mock
listenAddress: 0.0.0.0
ports:
- 443
- 8443
options:
tls: true
<<: *httpResponseRules
proxy:
handler: http_proxy
listenAddress: 0.0.0.0
ports:
- 3128
options:
target:
ipAddress: 127.0.0.1
port: 80
listeners:
udp_53:
name: ''
protocol: udp
listenAddress: ''
port: 1053
endpoints:
plainDns:
handler: dns_mock
listenAddress: 0.0.0.0
ports:
- 53
options:
rules:
- pattern: ".*\\.google\\.com"
@ -102,19 +92,75 @@ endpoints:
strategy: incremental
args:
startIP: 10.0.10.0
dnsOverTlsDowngrade:
handler: tls_interceptor
listenAddress: 0.0.0.0
ports:
- 853
tcp_80:
name: ''
protocol: tcp
listenAddress: ''
port: 80
<<: *httpHandlers
tcp_443:
name: ''
protocol: tcp
listenAddress: ''
port: 443
<<: *httpHandlers
tcp_853:
name: ''
protocol: tcp
listenAddress: ''
port: 853
endpoints:
DoT:
handler: dns_mock
tls: true
options:
rules:
- pattern: ".*\\.google\\.com"
response: 1.1.1.1
- pattern: ".*\\.reddit\\.com"
response: 2.2.2.2
fallback:
strategy: incremental
args:
startIP: 10.0.10.0
tcp_3128:
name: ''
protocol: tcp
listenAddress: ''
port: 3128
endpoints:
proxyPlain:
handler: http_proxy
options:
target:
ipAddress: 127.0.0.1
port: 53
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
listenAddress: 0.0.0.0
ports:
- 9110
options:
route: /metrics

12
go.mod
View file

@ -4,16 +4,20 @@ go 1.15
require (
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/protobuf v1.4.3
github.com/google/uuid v1.2.0
github.com/imdario/mergo v0.3.11
github.com/miekg/dns v1.1.35
github.com/olekukonko/tablewriter v0.0.4
github.com/jinzhu/copier v0.2.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/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/soheilhy/cmux v0.1.4
github.com/spf13/cobra v1.1.2
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/zap v1.16.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/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=
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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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/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/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
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.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/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-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=
@ -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/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
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/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/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/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/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/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/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=
@ -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/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/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/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/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
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-20201021153353-00ad82a08272/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e h1:/cwV7t2xezilMljIftb7WlFtzGANRCnoOhPjtl2ifcs=
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-20201021153353-00ad82a08272 h1:xOHQWEGsftkjjFV2KIrC9vOz+iOinvZ7H6EAjSznqUk=
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 h1:CQn2/8fi3kmpT9BTiHEELgdxAOQNVZc9GoPA4qnQzrs=
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.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.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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
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/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.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/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-kit/kit v0.8.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.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
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.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/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.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/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-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/mock v1.1.1/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.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.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.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
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/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.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
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 v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
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.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/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
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/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
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/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
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/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/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.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/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=
@ -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/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.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
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/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
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-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/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/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-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.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.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/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/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
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/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.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
github.com/miekg/dns v1.1.31/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/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw=
github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
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.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 v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
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/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/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-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
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/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.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
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.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.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/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/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
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/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.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/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/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/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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
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/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/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=
@ -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/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.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
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/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
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/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
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/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/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/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 v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
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/cobra v1.1.2 h1:frHO75w/dH7kEc+e2KYZZKY4+PLrp39OqI77oB8m0KQ=
github.com/spf13/cobra v1.1.2/go.mod h1:ZjwqWkCg0LnXvLRIfTLdB4Y/MCO3gMHHJ2KFxQZy4xE=
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.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.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/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.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
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.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
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/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-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.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
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.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
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.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=
@ -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/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/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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-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-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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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-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-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-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-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-20181108010431-42b317875d0f/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-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-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-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-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-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/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.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/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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
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-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-20180917221912-90fa682c2a6e/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-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-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=
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-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-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-20210126160654-44e461bb6506 h1:uLBY0yHDCj2PMQ98KWDSIDFwn9zK2zh+tgWtbvPPBjI=
google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28=
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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
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.26.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.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
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/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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
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/go.mod h1:xzjpkyedLMz3EXUTBbkRuuGPsxfsBX3Sy7J6kC9Gvoc=
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/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/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/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/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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/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-20190102054323-c2f93a96b099/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"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/audit/sink"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/health"
"gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/path"
"go.uber.org/multierr"
"go.uber.org/zap"
)
@ -41,12 +38,12 @@ const (
)
type App interface {
api.PluginContext
EventStream() audit.EventStream
Config() config.Config
Config() Config
Checker() health.Checker
EndpointManager() endpoint.EndpointManager
HandlerRegistry() api.HandlerRegistry
Logger() logging.Logger
EndpointManager() endpoint.Orchestrator
HandlerRegistry() endpoint.HandlerRegistry
Context() context.Context
RootCommand() *cobra.Command
MustRun()
@ -58,7 +55,7 @@ type App interface {
// WithHandlerRegistry builds up the handler registry
// requires nothing
WithHandlerRegistry(registrations ...api.Registration) App
WithHandlerRegistry(registrations ...endpoint.Registration) App
// WithHealthChecker adds the health checker mechanism
// requires nothing
@ -115,12 +112,12 @@ func (a *app) Logger() logging.Logger {
return val.(logging.Logger)
}
func (a *app) Config() config.Config {
func (a *app) Config() Config {
val := a.ctx.Value(configKey)
if val == nil {
return nil
}
return val.(config.Config)
return val.(Config)
}
func (a *app) CertStore() cert.Store {
@ -139,12 +136,12 @@ func (a *app) Checker() health.Checker {
return val.(health.Checker)
}
func (a *app) EndpointManager() endpoint.EndpointManager {
func (a *app) EndpointManager() endpoint.Orchestrator {
val := a.ctx.Value(endpointManagerKey)
if val == nil {
return nil
}
return val.(endpoint.EndpointManager)
return val.(endpoint.Orchestrator)
}
func (a *app) Audit() audit.Emitter {
@ -163,12 +160,12 @@ func (a *app) EventStream() audit.EventStream {
return val.(audit.EventStream)
}
func (a *app) HandlerRegistry() api.HandlerRegistry {
func (a *app) HandlerRegistry() endpoint.HandlerRegistry {
val := a.ctx.Value(handlerRegistryKey)
if val == nil {
return nil
}
return val.(api.HandlerRegistry)
return val.(endpoint.HandlerRegistry)
}
func (a *app) Context() context.Context {
@ -192,8 +189,8 @@ func (a *app) WithCommands(cmds ...*cobra.Command) App {
// WithHandlerRegistry builds up the handler registry
// requires nothing
func (a *app) WithHandlerRegistry(registrations ...api.Registration) App {
registry := api.NewHandlerRegistry()
func (a *app) WithHandlerRegistry(registrations ...endpoint.Registration) App {
registry := endpoint.NewHandlerRegistry()
for _, registration := range registrations {
if err := registration(registry); err != nil {
@ -242,11 +239,12 @@ func (a *app) WithLogger() App {
// requires WithHandlerRegistry, WithHealthChecker and WithLogger
func (a *app) WithEndpointManager() App {
a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) {
epMgr := endpoint.NewEndpointManager(
epMgr := endpoint.NewOrchestrator(
a.Context(),
a.CertStore(),
a.HandlerRegistry(),
a.Logger().Named("EndpointManager"),
a.Checker(),
a,
a.Audit(),
a.Logger().Named("Orchestrator"),
)
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) {
var certStore cert.Store
if certStore, err = cert.NewDefaultStore(
a.Config(),
a.Config().TLSConfig(),
a.Logger().Named("CertStore"),
); err != nil {
return
@ -310,7 +308,7 @@ func (a *app) WithEventStream() App {
// requires nothing
func (a *app) WithConfig() App {
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 {
return
}
@ -343,7 +341,9 @@ func NewApp(name, short string) App {
a.rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) {
for _, initTask := range a.lateInitTasks {
err = multierr.Append(err, initTask(cmd, args))
if err = initTask(cmd, args); err != nil {
return
}
}
return
}

View file

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

View file

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

View file

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

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"
"unsafe"
"github.com/spf13/viper"
"github.com/mitchellh/mapstructure"
)
const (
randomIPStrategyName = "random"
incrementalIPStrategyName = "incremental"
startIPConfigKey = "startIP"
)
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() {
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 {
func CreateResolverFallback(name string, args map[string]interface{}) ResolverFallback {
if factory, ok := fallbackStrategies[name]; ok {
return factory(config)
return factory(args)
} else {
return fallbackStrategies[randomIPStrategyName](config)
return fallbackStrategies[randomIPStrategyName](args)
}
}

View file

@ -2,36 +2,35 @@ package mock
import (
"context"
"time"
"github.com/miekg/dns"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
type dnsHandler struct {
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
if options, err = loadFromConfig(config.Options); err != nil {
if options, err = loadFromConfig(lifecycle); err != nil {
return
}
listenAddr := config.ListenAddr()
d.logger = pluginCtx.Logger().With(
zap.String("handler_name", config.HandlerName),
zap.String("address", listenAddr),
d.logger = lifecycle.Logger().With(
zap.String("handler_name", lifecycle.Name()),
zap.String("address", lifecycle.Uplink().Addr().String()),
)
handler := &regexHandler{
handlerName: config.HandlerName,
handlerName: lifecycle.Name(),
fallback: options.Fallback,
logger: pluginCtx.Logger(),
auditEmitter: pluginCtx.Audit(),
logger: lifecycle.Logger(),
auditEmitter: lifecycle.Audit(),
}
for _, rule := range options.Rules {
@ -43,31 +42,24 @@ func (d *dnsHandler) Start(pluginCtx api.PluginContext, config config.HandlerCon
handler.AddRule(rule)
}
d.logger = d.logger.With(
zap.String("address", listenAddr),
)
d.dnsServer = []*dns.Server{
{
Addr: listenAddr,
Net: "udp",
if lifecycle.Uplink().Listener != nil {
d.dnsServer = &dns.Server{
Listener: lifecycle.Uplink().Listener,
Handler: handler,
},
{
Addr: listenAddr,
Net: "tcp",
}
} else {
d.dnsServer = &dns.Server{
PacketConn: lifecycle.Uplink().PacketConn,
Handler: handler,
},
}
}
for _, dnsServer := range d.dnsServer {
go d.startServer(dnsServer)
}
go d.startServer()
return
}
func (d *dnsHandler) startServer(dnsServer *dns.Server) {
if err := dnsServer.ListenAndServe(); err != nil {
func (d *dnsHandler) startServer() {
if err := d.dnsServer.ActivateAndServe(); err != nil {
d.logger.Error(
"failed to start DNS server listener",
zap.Error(err),
@ -75,15 +67,11 @@ func (d *dnsHandler) startServer(dnsServer *dns.Server) {
}
}
func (d *dnsHandler) Shutdown(ctx context.Context) error {
d.logger.Info("shutting down DNS mock")
for _, dnsServer := range d.dnsServer {
if err := dnsServer.ShutdownContext(ctx); err != nil {
d.logger.Error(
"failed to shutdown server",
zap.Error(err),
)
func (d *dnsHandler) shutdownOnEnd(ctx context.Context) {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
if err := d.dnsServer.ShutdownContext(shutdownCtx); err != nil {
d.logger.Error("failed to shutdown DNS server", 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"
"regexp"
"github.com/spf13/viper"
)
const (
fallbackArgsConfigKey = "fallback.args"
"gitlab.com/inetmock/inetmock/internal/endpoint"
)
type resolverRule struct {
@ -21,7 +17,7 @@ type dnsOptions struct {
Fallback ResolverFallback
}
func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) {
func loadFromConfig(lifecycle endpoint.Lifecycle) (options dnsOptions, err error) {
type rule struct {
Pattern string
Response string
@ -29,6 +25,7 @@ func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) {
type fallback struct {
Strategy string
Args map[string]interface{}
}
opts := struct {
@ -36,7 +33,7 @@ func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) {
Fallback fallback
}{}
err = config.Unmarshal(&opts)
err = lifecycle.UnmarshalOptions(&opts)
for _, rule := range opts.Rules {
var err error
@ -53,7 +50,7 @@ func loadFromConfig(config *viper.Viper) (options dnsOptions, err error) {
options.Fallback = CreateResolverFallback(
opts.Fallback.Strategy,
config.Sub(fallbackArgsConfigKey),
opts.Fallback.Args,
)
return

View file

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

View file

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

View file

@ -2,7 +2,10 @@ package http
import (
"context"
"crypto/tls"
"net"
"github.com/soheilhy/cmux"
)
type httpContextKey string
@ -10,14 +13,35 @@ type httpContextKey string
const (
remoteAddrKey httpContextKey = "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/context/remoteAddr"
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 {
ctx = context.WithValue(ctx, remoteAddrKey, c.RemoteAddr())
ctx = context.WithValue(ctx, localAddrKey, c.LocalAddr())
ctx = addTLSConnectionStateToContext(ctx, c)
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 {
val := ctx.Value(localAddrKey)
if val == nil {

View file

@ -2,14 +2,13 @@ package mock
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"github.com/soheilhy/cmux"
"gitlab.com/inetmock/inetmock/internal/endpoint"
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"
"go.uber.org/zap"
)
@ -25,71 +24,62 @@ type httpHandler struct {
server *http.Server
}
func (p *httpHandler) Start(ctx api.PluginContext, config config.HandlerConfig) (err error) {
p.logger = ctx.Logger().With(
func (p *httpHandler) Matchers() []cmux.Matcher {
return []cmux.Matcher{cmux.HTTP1()}
}
func (p *httpHandler) Start(lifecycle endpoint.Lifecycle) (err error) {
p.logger = lifecycle.Logger().With(
zap.String("protocol_handler", name),
)
var options httpOptions
if options, err = loadFromConfig(config.Options); err != nil {
if options, err = loadFromConfig(lifecycle); err != nil {
return
}
p.logger = p.logger.With(
zap.String("handler_name", config.HandlerName),
zap.String("address", config.ListenAddr()),
zap.String("address", lifecycle.Uplink().Addr().String()),
)
router := &RegexpHandler{
logger: p.logger,
emitter: ctx.Audit(),
handlerName: config.HandlerName,
emitter: lifecycle.Audit(),
handlerName: lifecycle.Name(),
}
p.server = &http.Server{
Addr: config.ListenAddr(),
Handler: router,
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 {
router.setupRoute(rule)
}
go p.startServer(options.TLS)
go p.startServer(lifecycle.Uplink().Listener)
go p.shutdownOnCancel(lifecycle.Context())
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")
if err = p.server.Shutdown(ctx); err != nil {
if err := p.server.Close(); err != nil {
p.logger.Error(
"failed to shutdown HTTP server",
zap.Error(err),
)
err = fmt.Errorf(
"failed to shutdown HTTP server: %w",
err,
)
}
return
}
func (p *httpHandler) startServer(tls bool) {
var listen func() error
if tls {
listen = func() error {
return p.server.ListenAndServeTLS("", "")
func (p *httpHandler) startServer(listener net.Listener) {
defer func() {
if err := listener.Close(); err != nil {
p.logger.Warn("failed to close listener", zap.Error(err))
}
} else {
listen = p.server.ListenAndServe
}
if err := listen(); err != nil && !errors.Is(err, http.ErrServerClosed) {
}()
if err := p.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
p.logger.Error(
"failed to start http listener",
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"
"regexp"
"github.com/spf13/viper"
"gitlab.com/inetmock/inetmock/internal/endpoint"
)
var (
@ -50,11 +50,10 @@ func (tr targetRule) Response() string {
}
type httpOptions struct {
TLS bool
Rules []targetRule
}
func loadFromConfig(config *viper.Viper) (options httpOptions, err error) {
func loadFromConfig(lifecycle endpoint.Lifecycle) (options httpOptions, err error) {
type tmpCfg struct {
Pattern string
Response string
@ -63,16 +62,13 @@ func loadFromConfig(config *viper.Viper) (options httpOptions, err error) {
}
tmpRules := struct {
TLS bool
Rules []tmpCfg
}{}
if err = config.Unmarshal(&tmpRules); err != nil {
if err = lifecycle.UnmarshalOptions(&tmpRules); err != nil {
return
}
options.TLS = tmpRules.TLS
for _, i := range tmpRules.Rules {
var rulePattern *regexp.Regexp
var matchTargetValue RequestMatchTarget

View file

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

View file

@ -2,7 +2,7 @@ package mock
import (
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/pkg/api"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/metrics"
)
@ -11,7 +11,7 @@ var (
requestDurationHistogram *prometheus.HistogramVec
)
func AddHTTPMock(registry api.HandlerRegistry) (err error) {
func AddHTTPMock(registry endpoint.HandlerRegistry) (err error) {
if totalRequestCounter == nil {
if totalRequestCounter, err = metrics.Counter(
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{}
})

View file

@ -3,12 +3,12 @@ package proxy
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"github.com/soheilhy/cmux"
"gitlab.com/inetmock/inetmock/internal/endpoint"
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"
"go.uber.org/zap"
"gopkg.in/elazarl/goproxy.v1"
@ -24,46 +24,49 @@ type httpProxy struct {
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
if err = cfg.Options.Unmarshal(&opts); err != nil {
if err = lifecycle.UnmarshalOptions(&opts); err != nil {
return
}
listenAddr := cfg.ListenAddr()
h.server = &http.Server{
Addr: listenAddr,
Handler: h.proxy,
ConnContext: imHttp.StoreConnPropertiesInContext,
}
h.logger = h.logger.With(
zap.String("handler_name", cfg.HandlerName),
zap.String("address", listenAddr),
zap.String("handler_name", lifecycle.Name()),
zap.String("address", lifecycle.Uplink().Addr().String()),
)
tlsConfig := ctx.CertStore().TLSConfig()
tlsConfig := lifecycle.CertStore().TLSConfig()
proxyHandler := &proxyHttpHandler{
handlerName: cfg.HandlerName,
handlerName: lifecycle.Name(),
options: opts,
logger: h.logger,
emitter: ctx.Audit(),
emitter: lifecycle.Audit(),
}
proxyHTTPSHandler := &proxyHttpsHandler{
handlerName: cfg.HandlerName,
options: opts,
tlsConfig: tlsConfig,
logger: h.logger,
emitter: ctx.Audit(),
emitter: lifecycle.Audit(),
}
h.proxy.OnRequest().Do(proxyHandler)
h.proxy.OnRequest().HandleConnect(proxyHTTPSHandler)
go h.startProxy()
go h.startProxy(lifecycle.Uplink().Listener)
go h.shutdownOnContextDone(lifecycle.Context())
return
}
func (h *httpProxy) startProxy() {
if err := h.server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
func (h *httpProxy) startProxy(listener net.Listener) {
if err := h.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
h.logger.Error(
"failed to start proxy server",
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")
if err = h.server.Shutdown(ctx); err != nil {
if err = h.server.Close(); err != nil {
h.logger.Error(
"failed to shutdown proxy endpoint",
zap.Error(err),
)
err = fmt.Errorf(
"failed to shutdown proxy endpoint: %w",
err,
)
}
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 (
"crypto/tls"
"net/http"
"net/url"
"github.com/jinzhu/copier"
"github.com/prometheus/client_golang/prometheus"
imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http"
"gitlab.com/inetmock/inetmock/pkg/audit"
@ -13,6 +13,23 @@ import (
"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 {
handlerName string
options httpProxyOptions
@ -20,38 +37,19 @@ type proxyHttpHandler struct {
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) {
timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(p.handlerName))
defer timer.ObserveDuration()
totalRequestCounter.WithLabelValues(p.handlerName).Inc()
retReq = req
p.emitter.Emit(imHttp.EventFromRequest(req, audit.AppProtocol_HTTP_PROXY))
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(
"error while doing roundtrip",
zap.Error(err),
@ -62,35 +60,11 @@ func (p *proxyHttpHandler) Handle(req *http.Request, ctx *goproxy.ProxyCtx) (ret
return
}
func (p proxyHttpHandler) redirectHTTPRequest(originalRequest *http.Request) (redirectReq *http.Request) {
redirectReq = &http.Request{
Method: originalRequest.Method,
URL: &url.URL{
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,
func redirectHTTPRequest(targetHost string, originalRequest *http.Request) (redirectReq *http.Request, err error) {
redirectReq = new(http.Request)
if err = copier.Copy(redirectReq, originalRequest); err != nil {
return
}
redirectReq = redirectReq.WithContext(originalRequest.Context())
originalRequest.URL.Host = targetHost
return
}

View file

@ -2,7 +2,7 @@ package proxy
import (
"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/metrics"
"go.uber.org/zap"
@ -11,12 +11,10 @@ import (
var (
handlerNameLblName = "handler_name"
totalRequestCounter *prometheus.CounterVec
totalHttpsRequestCounter *prometheus.CounterVec
requestDurationHistogram *prometheus.HistogramVec
)
func AddHTTPProxy(registry api.HandlerRegistry) (err error) {
func AddHTTPProxy(registry endpoint.HandlerRegistry) (err error) {
var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil {
return
@ -25,19 +23,11 @@ func AddHTTPProxy(registry api.HandlerRegistry) (err error) {
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 {
return
}
if totalHttpsRequestCounter, err = metrics.Counter(name, "total_https_requests", "", handlerNameLblName); err != nil {
return
}
registry.RegisterHandler(name, func() api.ProtocolHandler {
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &httpProxy{
logger: logger,
proxy: goproxy.NewProxyHttpServer(),

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ import (
"github.com/google/uuid"
"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/metrics"
"go.uber.org/zap"
@ -18,7 +18,7 @@ var (
requestDurationHistogram *prometheus.HistogramVec
)
func AddTLSInterceptor(registry api.HandlerRegistry) (err error) {
func AddTLSInterceptor(registry endpoint.HandlerRegistry) (err error) {
var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil {
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{
logger: logger,
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 (
"net"
"net/url"
"os"
"time"
app2 "gitlab.com/inetmock/inetmock/internal/app"
@ -42,13 +43,6 @@ func (i *inetmockAPI) StartServer() (err error) {
}
i.server = grpc.NewServer()
RegisterHandlersServer(i.server, &handlersServer{
registry: i.app.HandlerRegistry(),
})
RegisterEndpointsServer(i.server, &endpointsServer{
endpointsManager: i.app.EndpointManager(),
})
RegisterHealthServer(i.server, &healthServer{
app: i.app,
})
@ -98,6 +92,11 @@ func (i *inetmockAPI) startServerAsync(listener net.Listener) {
func createListenerFromURL(url *url.URL) (lis net.Listener, err error) {
switch url.Scheme {
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)
default:
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
}
func (e *Event) ProtoMessage() *EventEntity {
func (e Event) ProtoMessage() *EventEntity {
var tlsDetails *TLSDetailsEntity = nil
if e.TLS != nil {
tlsDetails = e.TLS.ProtoMessage()

View file

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

View file

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

View file

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

View file

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

View file

@ -6,13 +6,14 @@ import (
)
func Test_certOptionsDefaulter(t *testing.T) {
tests := []struct {
type testCase struct {
name string
arg GenerationOptions
expected GenerationOptions
}{
}
tests := []testCase{
{
name: "",
name: "Empty options",
arg: GenerationOptions{
CommonName: "CA",
},
@ -26,14 +27,50 @@ func Test_certOptionsDefaulter(t *testing.T) {
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) {
return func(t *testing.T) {
if err := applyDefaultGenerationOptions(&tt.arg); err != nil {
t.Errorf("applyDefaultGenerationOptions() error = %v", err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
certOptionsDefaulter(&tt.arg)
if !reflect.DeepEqual(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"
"math/big"
"net"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/defaulting"
)
var (
defaulters = defaulting.New()
)
type GenerationOptions struct {
@ -37,11 +30,11 @@ type Generator interface {
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))
}
func NewGenerator(options config.CertOptions, source TimeSource, provider KeyProvider) Generator {
func NewGenerator(options CertOptions, source TimeSource, provider KeyProvider) Generator {
return &generator{
options: options,
provider: provider,
@ -50,7 +43,7 @@ func NewGenerator(options config.CertOptions, source TimeSource, provider KeyPro
}
type generator struct {
options config.CertOptions
options CertOptions
provider KeyProvider
timeSource TimeSource
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) {
defaulters.Apply(&options)
applyDefaultGenerationOptions(&options)
var serialNumber *big.Int
if serialNumber, err = generateSerialNumber(); err != nil {
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) {
defaulters.Apply(&options)
applyDefaultGenerationOptions(&options)
var privateKey interface{}
var serialNumber *big.Int

View file

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

View file

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

View file

@ -8,7 +8,6 @@ import (
"crypto/x509"
"net"
"gitlab.com/inetmock/inetmock/pkg/config"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
@ -18,7 +17,7 @@ const (
)
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 privateKeyForCurve(options)
}
@ -34,20 +33,20 @@ type Store interface {
}
func NewDefaultStore(
config config.Config,
options CertOptions,
logger logging.Logger,
) (Store, error) {
timeSource := NewTimeSource()
return NewStore(
config.TLSConfig(),
NewFileSystemCache(config.TLSConfig().CertCachePath, timeSource),
NewDefaultGenerator(config.TLSConfig()),
options,
NewFileSystemCache(options.CertCachePath, timeSource),
NewDefaultGenerator(options),
logger,
)
}
func NewStore(
options config.CertOptions,
options CertOptions,
cache Cache,
generator Generator,
logger logging.Logger,
@ -67,7 +66,7 @@ func NewStore(
}
type store struct {
options config.CertOptions
options CertOptions
caCert *tls.Certificate
cache Cache
timeSource TimeSource
@ -147,15 +146,15 @@ func (s *store) GetCertificate(serverName string, ip string) (cert *tls.Certific
return
}
func privateKeyForCurve(options config.CertOptions) (privateKey interface{}, err error) {
func privateKeyForCurve(options CertOptions) (privateKey interface{}, err error) {
switch options.Curve {
case config.CurveTypeP224:
case CurveTypeP224:
privateKey, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
case config.CurveTypeP256:
case CurveTypeP256:
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
case config.CurveTypeP384:
case CurveTypeP384:
privateKey, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
case config.CurveTypeP521:
case CurveTypeP521:
privateKey, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
default:
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) {
if zapLogger, err := loggingConfig.Build(); err != nil {
if zapLogger, err := loggingConfig.Build(zap.AddCallerSkip(2)); err != nil {
return nil, err
} else {
return NewLogger(zapLogger), nil
@ -55,11 +55,3 @@ func CreateTestLogger(tb testing.TB) Logger {
encoder: zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()),
}
}
func MustCreateLogger() Logger {
if logger, err := CreateLogger(); err != nil {
panic(err)
} else {
return logger
}
}

View file

@ -19,6 +19,7 @@ type testLogger struct {
func (t testLogger) Named(s string) Logger {
return testLogger{
encoder: t.encoder,
name: s,
tb: t.tb,
fields: t.fields,
@ -27,6 +28,7 @@ func (t testLogger) Named(s string) Logger {
func (t testLogger) With(fields ...zap.Field) Logger {
return &testLogger{
encoder: t.encoder,
name: t.name,
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