Merge commit '9a3c410ee8dfe2a380dc1ab36a5778e2c0f7049b' as 'api'

This commit is contained in:
Peter 2021-02-15 16:48:06 +01:00
commit 80fa8f45b9
154 changed files with 223 additions and 10159 deletions

View file

@ -1,28 +0,0 @@
###############
# Directories #
###############
.git/
.github/
.idea/
.vscode/
deploy/
dist/
doc/
#########
# Files #
#########
.gitattributes
.gitignore
.dockerignore
.goreleaser.yml
*.out
main
inetmock
imctl
README.md
LICENSE
Dockerfile
config.yaml

View file

@ -1,23 +1,10 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
insert_final_newline = true
charset = utf-8
[*.proto]
indent_style = space
insert_final_newline = false
max_line_length = 120
tab_width = 4
[{*.bash, *.sh, *.zsh}]
indent_size = 2
tab_width = 2
[{*.go, *.go2}]
indent_style = tab
[{*.har, *.jsb2, *.jsb3, *.json, .babelrc, .eslintrc, .stylelintrc, bowerrc, jest.config}]
indent_size = 2
[{*.yaml, *.yml}]
indent_size = 2
indent_size = 4

1
.gitattributes vendored
View file

@ -1 +0,0 @@
assets/fakeFiles/* filter=lfs diff=lfs merge=lfs -text

View file

@ -1,57 +0,0 @@
name: Docker Image CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
IMAGE_NAME: server
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
lfs: true
- name: Login to GitHub Docker registry
run: echo ${{ secrets.GITHUB_TOKEN }} | docker login docker.pkg.github.com -u baez90 --password-stdin
- name: Build the Docker image
run: docker build . --file inetmock.dockerfile --tag $IMAGE_NAME
- name: Push image to GitHub packages
run: |
IMAGE_ID=docker.pkg.github.com/${{ github.repository }}/$IMAGE_NAME
# Change all uppercase to lowercase
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
# Strip git ref prefix from version
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
# Strip "v" prefix from tag name
[[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
# Use Docker `latest` tag convention
[ "$VERSION" == "master" ] && VERSION=latest
echo IMAGE_ID=$IMAGE_ID
echo VERSION=$VERSION
docker tag $IMAGE_NAME $IMAGE_ID:$VERSION
docker push $IMAGE_ID:$VERSION
- name: Tag image for Docker Hub
run: docker tag $IMAGE_NAME ${GITHUB_REPOSITORY}:latest
- name: Push latest tag to Docker Hub
uses: docker/build-push-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: baez90/inetmock
tags: latest

View file

@ -1,49 +0,0 @@
name: Go
on: [ push, pull_request ]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.15
uses: actions/setup-go@v2
with:
go-version: '^1.15'
id: go
- name: Install Protoc
uses: arduino/setup-protoc@master
with:
version: '3.x'
- name: Check out code into the Go module directory
uses: actions/checkout@v2
with:
lfs: true
- name: Install mockgen
run: go get -u github.com/golang/mock/mockgen@latest
- name: Install go-enuum
run: go get -u github.com/abice/go-enum
- name: Install protoc-gen-go
run: go install github.com/golang/protobuf/protoc-gen-go
- name: Unshallow
run: git fetch --prune --unshallow
- name: Build & test
run: make
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

26
.gitignore vendored
View file

@ -1,26 +1,2 @@
#########
# files #
#########
**/cov.out
**/cov-raw.out
**/*.so
*.key
*.pem
/inetmock
/imctl
./main
**/*.mock.go
**/*_enum.go
**/*.pb.go
###############
# directories #
###############
.idea/
.vscode/
dist/
out/
.task/
public/
.idea/

View file

@ -1,65 +0,0 @@
image: registry.gitlab.com/inetmock/ci-image/go
stages:
- test
- build
- release
- deploy
test:
stage: test
script:
- task cli-cover-report
artifacts:
reports:
junit: out/report.xml
cobertura: out/coverage.xml
integration-test:
stage: test
services:
- docker:dind
script:
- task integration-test
lint:
stage: test
script:
- golangci-lint run
allow_failure: true
snapshot-release:
stage: build
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- task snapshot-release
except:
- tags
release:
stage: release
services:
- docker:dind
only:
- tags
variables:
GIT_DEPTH: 0
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- goreleaser release --rm-dist
pages:
stage: deploy
image: registry.gitlab.com/inetmock/ci-image/mdbook
only:
refs:
- master
- tags
script:
- mdbook build -d ./../public ./docs
artifacts:
paths:
- public

View file

@ -1,3 +0,0 @@
terminal:
image: registry.gitlab.com/inetmock/ci-image
script: sleep 60

View file

@ -1,136 +0,0 @@
linters-settings:
depguard:
list-type: blacklist
packages:
# logging is allowed only by logutils.Log, logrus
# is allowed to use only in logutils package
- github.com/sirupsen/logrus
packages-with-error-message:
- github.com/sirupsen/logrus: "logging is allowed only by logutils.Log"
dupl:
threshold: 100
funlen:
lines: 100
statements: 50
gci:
local-prefixes: gitlab.com/inetmock/inetmock
goconst:
min-len: 2
min-occurrences: 2
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
disabled-checks:
- dupImport # https://github.com/go-critic/go-critic/issues/845
- ifElseChain
- octalLiteral
- whyNoLint
- wrapperFunc
gocyclo:
min-complexity: 15
goimports:
local-prefixes: gitlab.com/inetmock/inetmock
golint:
min-confidence: 0
gomnd:
settings:
mnd:
# don't include the "operation" and "assign"
checks: argument,case,condition,return
govet:
check-shadowing: true
lll:
line-length: 140
maligned:
suggest-new: true
misspell:
locale: US
nolintlint:
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
allow-unused: false # report any unused nolint directives
require-explanation: false # don't require an explanation for nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
linters:
# please, do not use `enable-all`: it's deprecated and will be removed soon.
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- exhaustive
- funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- golint
- gomnd
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- misspell
- nakedret
- noctx
- nolintlint
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace
# don't enable:
# - asciicheck
# - gochecknoglobals
# - gocognit
# - godot
# - godox
# - goerr113
# - maligned
# - nestif
# - prealloc
# - testpackage
# - wsl
issues:
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
- path: _test\.go
linters:
- gomnd
# https://github.com/go-critic/go-critic/issues/926
- linters:
- gocritic
text: "unnecessaryDefer:"
run:
skip-dirs:
- internal/mock
# golangci.com configuration
# https://github.com/golangci/golangci/wiki/Configuration
service:
golangci-lint-version: 1.23.x # use the fixed version to not introduce new linters unexpectedly
prepare:
- echo "here I can run custom commands, but no preparation needed for this repo"

View file

@ -1,84 +0,0 @@
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
before:
hooks:
# You may remove this if you don't use go modules.
- task generate
builds:
- id: "inetmock"
binary: inetmock
main: ./cmd/inetmock/
ldflags:
- -w -s
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
goarch:
- amd64
- id: "imctl"
binary: imctl
main: ./cmd/imctl/
ldflags:
- -w -s
goos:
- linux
- freebsd
- darwin
- windows
goarch:
- amd64
archives:
- id: inetmock
builds:
- inetmock
name_template: "{{ .ProjectName }}_server_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
replacements:
amd64: x86_64
wrap_in_directory: true
files:
- config.yaml
- "*.so"
- id: imctl
builds:
- imctl
name_template: "{{ .ProjectName }}_cli_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
replacements:
amd64: x86_64
wrap_in_directory: true
files: [ ]
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
release:
gitlab:
owner: inetmock
name: inetmock
dockers:
- ids:
- inetmock
- imctl
image_templates:
- registry.gitlab.com/inetmock/inetmock:latest
- registry.gitlab.com/inetmock/inetmock:{{ .Tag }}
- registry.gitlab.com/inetmock/inetmock:{{ .Major }}
dockerfile: build/docker/inetmock.dockerfile
build_flag_templates:
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
extra_files:
- config-container.yaml
- assets/

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Peter Kurfer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,30 +1 @@
# INetMock
[![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)
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.
INetMock right now does **not** implement so many protocols like INetSim. In fact it is only able to respond to HTTP,
HTTPS, DNS, DNS-over-TLS (DoT) requests and to act as an HTTP proxy. The most notable advantage of INetMOck over INetSim
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.
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!_
## Docs
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
Please create an issue for any proposal, feature requests, found bug,... I'm glad for every kind of feedback!
Right now I've no special workflow for pull requests but I will look into every proposed change.
# INetMock protobuf API

View file

@ -1,116 +0,0 @@
version: '3'
vars:
OUT_DIR: ./out
INETMOCK_PKG: gitlab.com/inetmock/inetmock/cmd/inetmock
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
GOARCH: amd64
CGO_ENABLED: 0
tasks:
clean:
cmds:
- find . -type f \( -name "*.pb.go" -or -name "*.mock.go" \) -exec rm -f {} \;
- rm -rf ./main {{ .OUT_DIR }}
format:
cmds:
- go fmt ./...
protoc:
sources:
- "**/*.proto"
cmds:
- protoc --proto_path ./api/proto/ --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative {{ .PROTO_FILES }}
go-generate:
sources:
- "**/*.go"
cmds:
- go generate -x ./...
generate:
deps:
- go-generate
- protoc
test:
sources:
- "**/*.go"
deps:
- generate
cmds:
- mkdir -p {{ .OUT_DIR }}
- cmd: go test -coverprofile={{ .OUT_DIR }}/cov-raw.out -covermode count -v ./... 2>&1 | tee {{ .OUT_DIR }}/test_output
ignore_error: true
- cat {{ .OUT_DIR }}/test_output | go-junit-report -set-exit-code > {{ .OUT_DIR }}/report.xml
- grep -v "generated" {{ .OUT_DIR }}/cov-raw.out > {{ .OUT_DIR }}/cov.out
- 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
cmds:
- go tool cover -func={{ .OUT_DIR }}/cov.out
html-cover-report:
deps:
- test
cmds:
- go tool cover -html={{ .OUT_DIR }}/cov.out -o {{ .OUT_DIR }}/coverage.html
build-inetmock:
deps:
- test
cmds:
- mkdir -p {{ .OUT_DIR }}
- go build -ldflags='-w -s' -o {{ .OUT_DIR }}/inetmock {{ .INETMOCK_PKG }}
debug-inetmock:
cmds:
- dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient --output {{ .OUT_DIR }}/__debug_bin debug {{ .INETMOCK_PKG }} -- serve
build-imctl:
deps:
- test
cmds:
- mkdir -p {{ .OUT_DIR }}
- go build -ldflags='-w -s' -o {{ .OUT_DIR }}/imctl {{ .IMCTL_PKG }}
build-all:
deps:
- build-inetmock
- build-imctl
snapshot-release:
deps:
- test
cmds:
- goreleaser release --snapshot --skip-publish --rm-dist
release:
deps:
- test
cmds:
- goreleaser release
docs:
cmds:
- mdbook build -d ./../public ./docs

View file

@ -1,5 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEk8KFe6Vl3xefH82
Na91uPQkBz4D/s1xE+59+kaRM1+hRANCAASnjLNiOc4UNsRBIQN+FOv8CEd6ftDH
Egg2dWUgGCFsgE2VaEG+jOwamBzTjCWbr0azIc+Brupd0CAChq8hVeaM
-----END PRIVATE KEY-----

View file

@ -1,14 +0,0 @@
-----BEGIN CERTIFICATE-----
MIICGjCCAb+gAwIBAgIQZutydXTVGWPa32GhLwn4tDAKBggqhkjOPQQDAjBcMRAw
DgYDVQQGEwdnZXJtYW55MQwwCgYDVQQIEwNOUlcxETAPBgNVBAcTCERvcnRtdW5k
MREwDwYDVQQKEwhJTmV0TW9jazEUMBIGA1UEAxMLSU5ldE1vY2sgQ0EwIBcNMDEw
MjAxMDgyODA5WhgPMjA1MTAxMjAwODI4MDlaMFwxEDAOBgNVBAYTB2dlcm1hbnkx
DDAKBgNVBAgTA05SVzERMA8GA1UEBxMIRG9ydG11bmQxETAPBgNVBAoTCElOZXRN
b2NrMRQwEgYDVQQDEwtJTmV0TW9jayBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABKeMs2I5zhQ2xEEhA34U6/wIR3p+0McSCDZ1ZSAYIWyATZVoQb6M7BqYHNOM
JZuvRrMhz4Gu6l3QIAKGryFV5oyjYTBfMA4GA1UdDwEB/wQEAwIChDAdBgNVHSUE
FjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUkD5REm4Z34O8em3l5cXmHgd3S4swCgYIKoZIzj0EAwIDSQAwRgIhAJ5xWTz/
/51Jyo7sO4hi3FhyDYJXeC0koRSQCvMggWuPAiEAjpgtX/UFukTAfTFsDp8q3AXZ
Kn0ejVWjmkh4K7nEJ5s=
-----END CERTIFICATE-----

BIN
assets/fakeFiles/default.gif (Stored with Git LFS)

Binary file not shown.

BIN
assets/fakeFiles/default.html (Stored with Git LFS)

Binary file not shown.

BIN
assets/fakeFiles/default.ico (Stored with Git LFS)

Binary file not shown.

BIN
assets/fakeFiles/default.jpg (Stored with Git LFS)

Binary file not shown.

BIN
assets/fakeFiles/default.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/fakeFiles/default.txt (Stored with Git LFS)

Binary file not shown.

View file

@ -1,41 +0,0 @@
# Runtime layer
FROM alpine:3.13
# Create appuser and group.
ARG USER=inetmock
ARG GROUP=inetmock
ARG USER_ID=10001
ARG GROUP_ID=10001
RUN addgroup -S -g "${GROUP_ID}" "${GROUP}" && \
adduser \
--disabled-password \
--gecos "" \
--home "/nonexistent" \
--shell "/sbin/nologin" \
--no-create-home \
-G "${GROUP}" \
--uid "${USER_ID}" \
"${USER}"
COPY --chown=$USER:$GROUP inetmock imctl /usr/lib/inetmock/bin/
COPY --chown=$USER:$GROUP assets/fakeFiles /var/lib/inetmock/fakeFiles/
COPY --chown=$USER:$GROUP assets/demoCA /var/lib/inetmock/ca
COPY config-container.yaml /etc/inetmock/config.yaml
RUN mkdir -p /var/run/inetmock /var/lib/inetmock/certs /usr/lib/inetmock && \
chown -R $USER:$GROUP /var/run/inetmock /var/lib/inetmock /usr/lib/inetmock && \
apk add -U --no-cache libcap
RUN ln -s /usr/lib/inetmock/bin/inetmock /usr/bin/inetmock && \
ln -s /usr/lib/inetmock/bin/imctl /usr/bin/imctl && \
setcap 'cap_net_bind_service=+ep' /usr/lib/inetmock/bin/inetmock
HEALTHCHECK --interval=5s --timeout=1s \
CMD imctl --socket-path /var/run/inetmock/inetmock.sock health container
USER $USER
VOLUME [ "/var/lib/inetmock/ca", "/var/lib/inetmock/certs" ]
ENTRYPOINT ["inetmock"]

View file

@ -1,118 +0,0 @@
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/format"
"gitlab.com/inetmock/inetmock/internal/rpc"
"gitlab.com/inetmock/inetmock/pkg/audit"
)
var (
listSinksCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls", "dir"},
Short: "List all subscribed sinks",
RunE: runListSinks,
}
addFileCmd = &cobra.Command{
Use: "add-file",
Aliases: []string{"add"},
Short: "subscribe events to a file",
Args: cobra.ExactArgs(1),
RunE: runAddFile,
}
removeFileCmd = &cobra.Command{
Use: "remove-file",
Aliases: []string{"rm", "del"},
Short: "remove file subscription",
Args: cobra.ExactArgs(1),
RunE: runRemoveFile,
}
readFileCmd = &cobra.Command{
Use: "read-file",
Aliases: []string{"cat"},
Short: "reads an audit file and prints the events",
Args: cobra.ExactArgs(1),
RunE: runReadFile,
}
)
type printableSink struct {
Name string
}
func runListSinks(*cobra.Command, []string) (err error) {
auditClient := rpc.NewAuditClient(conn)
ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
defer cancel()
var resp *rpc.ListSinksResponse
if resp, err = auditClient.ListSinks(ctx, &rpc.ListSinksRequest{}); err != nil {
return
}
var sinks []printableSink
for _, s := range resp.Sinks {
sinks = append(sinks, printableSink{Name: s})
}
writer := format.Writer(outputFormat, os.Stdout)
err = writer.Write(sinks)
return
}
func runAddFile(_ *cobra.Command, args []string) (err error) {
auditClient := rpc.NewAuditClient(conn)
ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
defer cancel()
_, err = auditClient.RegisterFileSink(ctx, &rpc.RegisterFileSinkRequest{TargetPath: args[0]})
return
}
func runRemoveFile(_ *cobra.Command, args []string) (err error) {
auditClient := rpc.NewAuditClient(conn)
ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
defer cancel()
_, err = auditClient.RemoveFileSink(ctx, &rpc.RemoveFileSinkRequest{TargetPath: args[0]})
return
}
func runReadFile(_ *cobra.Command, args []string) (err error) {
if len(args) != 1 {
return errors.New("expected only 1 argument")
}
var reader io.ReadCloser
if reader, err = os.Open(args[0]); err != nil {
return
}
eventReader := audit.NewEventReader(reader)
var ev audit.Event
for err == nil {
if ev, err = eventReader.Read(); err == nil {
var jsonBytes []byte
if jsonBytes, err = json.Marshal(ev); err == nil {
fmt.Println(string(jsonBytes))
}
}
}
if errors.Is(err, io.EOF) {
err = nil
}
return
}

View file

@ -1,54 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/rpc"
"gitlab.com/inetmock/inetmock/pkg/audit"
)
var (
watchEventsCmd = &cobra.Command{
Use: "watch",
Short: "Watch all audit events",
RunE: watchAuditEvents,
}
auditCmd = &cobra.Command{
Use: "audit",
Short: "Interact with the audit stream",
}
listenerName string
)
func watchAuditEvents(_ *cobra.Command, _ []string) (err error) {
auditClient := rpc.NewAuditClient(conn)
var watchClient rpc.Audit_WatchEventsClient
if watchClient, err = auditClient.WatchEvents(cliApp.Context(), &rpc.WatchEventsRequest{WatcherName: listenerName}); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
go func() {
var protoEv *audit.EventEntity
for protoEv, err = watchClient.Recv(); err == nil; protoEv, err = watchClient.Recv() {
ev := audit.NewEventFromProto(protoEv)
var out []byte
out, err = json.Marshal(ev)
if err != nil {
continue
}
fmt.Println(string(out))
}
}()
<-cliApp.Context().Done()
err = watchClient.CloseSend()
return
}

View file

@ -1,89 +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 (
healthCmd = &cobra.Command{
Use: "health",
Short: "health is the entry point for all health check related commands",
}
generalHealthCmd = &cobra.Command{
Use: "general",
Short: "get the health in a more general way i.e. exit code 0 if healthy, exit codes unequal 0 if somethings wrong",
Long: `
Exit code 1 means the server is still initializing
Exit code 2 means any component is unhealthy
Exit code 10 means an error occurred while opening a connection to the API socket
The output contains information about each component and it's health state.
`,
Run: runGeneralHealth,
}
containerHealthCmd = &cobra.Command{
Use: "container",
Short: "get the health in a container compatible way i.e. exit code 0 if okay otherwise exit code 1",
Run: runContainerHealth,
}
)
type printableHealthInfo struct {
Component string
State string
Message string
}
func fromComponentsHealth(componentsHealth map[string]*rpc.ComponentHealth) (componentsInfo []printableHealthInfo) {
for componentName, component := range componentsHealth {
componentsInfo = append(componentsInfo, printableHealthInfo{
Component: componentName,
State: component.State.String(),
Message: component.Message,
})
}
return
}
func getHealthResult() (healthResp *rpc.HealthResponse, err error) {
var healthClient = rpc.NewHealthClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), grpcTimeout)
healthResp, err = healthClient.GetHealth(ctx, &rpc.HealthRequest{})
cancel()
return
}
func runGeneralHealth(_ *cobra.Command, _ []string) {
var healthResp *rpc.HealthResponse
var err error
if healthResp, err = getHealthResult(); err != nil {
fmt.Printf("Failed to get health information: %v", err)
os.Exit(1)
}
printable := fromComponentsHealth(healthResp.ComponentsHealth)
writer := format.Writer(outputFormat, os.Stdout)
if err = writer.Write(printable); err != nil {
fmt.Printf("Error occurred during writing response values: %v\n", err)
}
}
func runContainerHealth(_ *cobra.Command, _ []string) {
if healthResp, err := getHealthResult(); err != nil {
fmt.Printf("Failed to get health information: %v", err)
os.Exit(1)
} else if healthResp.OverallHealthState != rpc.HealthState_HEALTHY {
fmt.Println("Overall health state is not healthy")
os.Exit(1)
}
}

View file

@ -1,62 +0,0 @@
package main
import (
"context"
"fmt"
"os"
"os/user"
"time"
"github.com/google/uuid"
"github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/app"
"google.golang.org/grpc"
)
var (
inetMockSocketPath string
outputFormat string
grpcTimeout time.Duration
cliApp app.App
conn *grpc.ClientConn
)
func main() {
healthCmd.AddCommand(generalHealthCmd, containerHealthCmd)
cliApp = app.NewApp("imctl", "IMCTL is the CLI app to interact with an INetMock server").
WithCommands(healthCmd, auditCmd).
WithInitTasks(func(_ *cobra.Command, _ []string) (err error) {
return initGRPCConnection()
}).
WithLogger()
cliApp.RootCommand().PersistentFlags().StringVar(&inetMockSocketPath, "socket-path", "unix:///var/run/inetmock.sock", "Path to the INetMock socket file")
cliApp.RootCommand().PersistentFlags().StringVarP(&outputFormat, "format", "f", "table", "Output format to use. Possible values: table, json, yaml")
cliApp.RootCommand().PersistentFlags().DurationVar(&grpcTimeout, "grpc-timeout", 5*time.Second, "Timeout to connect to the gRPC API")
currentUser := ""
if usr, err := user.Current(); err == nil {
currentUser = usr.Username
} else {
currentUser = uuid.New().String()
}
hostname := "."
if hn, err := os.Hostname(); err == nil {
hostname = hn
}
watchEventsCmd.PersistentFlags().StringVar(&listenerName, "listener-name", fmt.Sprintf("%s\\%s is watching", hostname, currentUser), "set listener name - defaults to the current username, if the user cannot be determined a random UUID will be used")
auditCmd.AddCommand(listSinksCmd, watchEventsCmd, addFileCmd, removeFileCmd, readFileCmd)
cliApp.MustRun()
}
func initGRPCConnection() (err error) {
dialCtx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
conn, err = grpc.DialContext(dialCtx, inetMockSocketPath, grpc.WithInsecure())
cancel()
return
}

View file

@ -1,109 +0,0 @@
package main
import (
"crypto/tls"
"crypto/x509"
"time"
"github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/pkg/cert"
"go.uber.org/zap"
)
const (
generateCACommonName = "cn"
generateCaOrganizationName = "o"
generateCaOrganizationalUnitName = "ou"
generateCaCountryName = "c"
generateCaLocalityName = "l"
generateCaStateName = "st"
generateCaStreetAddressName = "street-address"
generateCaPostalCodeName = "postal-code"
generateCACertOutPath = "out-dir"
generateCACurveName = "curve"
generateCANotBeforeRelative = "not-before"
generateCANotAfterRelative = "not-after"
)
var (
generateCaCmd *cobra.Command
caCertOptions cert.GenerationOptions
notBefore, notAfter time.Duration
certOutPath, curveName string
)
//nolint:lll
func init() {
generateCaCmd = &cobra.Command{
Use: "generate-ca",
Short: "Generate a new CA certificate and corresponding key",
Long: ``,
Run: runGenerateCA,
}
generateCaCmd.Flags().StringVar(&caCertOptions.CommonName, generateCACommonName, "INetMock", "Certificate Common Name that will also be used as file name during generation.")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.Organization, generateCaOrganizationName, nil, "Organization information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.OrganizationalUnit, generateCaOrganizationalUnitName, nil, "Organizational unit information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.Country, generateCaCountryName, nil, "Country information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.Province, generateCaStateName, nil, "State information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.Locality, generateCaLocalityName, nil, "Locality information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.StreetAddress, generateCaStreetAddressName, nil, "Street address information to append to certificate")
generateCaCmd.Flags().StringSliceVar(&caCertOptions.PostalCode, generateCaPostalCodeName, nil, "Postal code information to append to certificate")
generateCaCmd.Flags().StringVar(&certOutPath, generateCACertOutPath, "", "Path where CA files should be stored")
generateCaCmd.Flags().StringVar(&curveName, generateCACurveName, "", "Name of the curve to use, if empty ED25519 is used, other valid values are [P224, P256,P384,P521]")
generateCaCmd.Flags().DurationVar(&notBefore, generateCANotBeforeRelative, 17520*time.Hour, "Relative time value since when in the past the CA certificate should be valid. The value has a time unit, the greatest time unit is h for hour.")
generateCaCmd.Flags().DurationVar(&notAfter, generateCANotAfterRelative, 17520*time.Hour, "Relative time value until when in the future the CA certificate should be valid. The value has a time unit, the greatest time unit is h for hour.")
}
func runGenerateCA(_ *cobra.Command, _ []string) {
logger := serverApp.Logger().Named("generate-ca")
logger = logger.With(
zap.String(generateCACurveName, curveName),
zap.String(generateCACertOutPath, certOutPath),
)
generator := cert.NewDefaultGenerator(cert.CertOptions{
CertCachePath: certOutPath,
Curve: cert.CurveType(curveName),
Validity: cert.ValidityByPurpose{
CA: cert.ValidityDuration{
NotAfterRelative: notAfter,
NotBeforeRelative: notBefore,
},
},
})
var err error
var caCrt *tls.Certificate
if caCrt, err = generator.CACert(caCertOptions); err != nil {
logger.Error(
"failed to generate CA certificate",
zap.Error(err),
)
return
}
if len(caCrt.Certificate) < 1 {
logger.Error("no public key given for generated CA certificate")
return
}
var pubKey *x509.Certificate
if pubKey, err = x509.ParseCertificate(caCrt.Certificate[0]); err != nil {
logger.Error(
"failed to parse public key from generated CA",
zap.Error(err),
)
return
}
pemCrt := cert.NewPEM(caCrt)
if err = pemCrt.Write(pubKey.Subject.CommonName, certOutPath); err != nil {
logger.Error(
"failed to write Ca files",
zap.Error(err),
)
}
logger.Info("completed certificate generation")
}

View file

@ -1,33 +0,0 @@
package main
import (
"gitlab.com/inetmock/inetmock/internal/app"
dns "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
http "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/proxy"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/metrics"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/tls/interceptor"
)
var (
serverApp app.App
)
func main() {
serverApp = app.NewApp("inetmock", "INetMock is lightweight internet mock").
WithHandlerRegistry(
http.AddHTTPMock,
dns.AddDNSMock,
interceptor.AddTLSInterceptor,
proxy.AddHTTPProxy,
metrics.AddMetricsExporter).
WithCommands(serveCmd, generateCaCmd).
WithConfig().
WithLogger().
WithHealthChecker().
WithCertStore().
WithEventStream().
WithEndpointManager()
serverApp.MustRun()
}

View file

@ -1,58 +0,0 @@
package main
import (
"github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/rpc"
"go.uber.org/zap"
)
var (
serveCmd = &cobra.Command{
Use: "serve",
Short: "Starts the INetMock server",
Long: ``,
RunE: startINetMock,
}
)
func startINetMock(_ *cobra.Command, _ []string) (err error) {
rpcAPI := rpc.NewINetMockAPI(serverApp)
logger := serverApp.Logger()
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
}
}
errChan := serverApp.EndpointManager().StartEndpoints()
if err = rpcAPI.StartServer(); err != nil {
serverApp.Shutdown()
logger.Error(
"failed to start gRPC API",
zap.Error(err),
)
}
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")
rpcAPI.StopServer()
return
}

View file

@ -1,166 +0,0 @@
x-response-rules: &httpResponseRules
rules:
- pattern: ".*\\.(?i)exe"
matcher: Path
- pattern: "^application/octet-stream$"
target: Accept
matcher: Header
response: /var/lib/inetmock/fakeFiles/sample.exe
- pattern: "^image/jpeg$"
target: Accept
matcher: Header
response: /var/lib/inetmock/fakeFiles/default.jpg
- pattern: ".*\\.(?i)(jpg|jpeg)"
matcher: Path
response: /var/lib/inetmock/fakeFiles/default.jpg
- pattern: "^image/png$"
target: Accept
matcher: Header
response: /var/lib/inetmock/fakeFiles/default.png
- pattern: ".*\\.(?i)png"
matcher: Path
response: /var/lib/inetmock/fakeFiles/default.png
- pattern: ".*\\.(?i)gif"
matcher: Path
response: /var/lib/inetmock/fakeFiles/default.gif
- pattern: ".*\\.(?i)ico"
matcher: Path
response: /var/lib/inetmock/fakeFiles/default.ico
- pattern: "^text/plain$"
target: Accept
matcher: Header
response: /var/lib/inetmock/fakeFiles/default.txt
- pattern: ".*\\.(?i)txt"
matcher: Path
response: /var/lib/inetmock/fakeFiles/default.txt
- pattern: "^text/html$"
target: Accept
matcher: Header
response: /var/lib/inetmock/fakeFiles/default.html
- pattern: ".*"
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
tls:
curve: P256
minTLSVersion: SSL3
includeInsecureCipherSuites: false
validity:
ca:
notBeforeRelative: 17520h
notAfterRelative: 17520h
server:
NotBeforeRelative: 168h
NotAfterRelative: 168h
rootCaCert:
publicKeyPath: /var/lib/inetmock/ca/ca.pem
privateKeyPath: /var/lib/inetmock/ca/ca.key
certCachePath: /var/lib/inetmock/certs
listeners:
udp_53:
name: ''
protocol: udp
listenAddress: ''
port: 53
endpoints:
plainDns:
handler: dns_mock
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_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: 80
proxyTls:
handler: http_proxy
tls: true
options:
target:
ipAddress: 127.0.0.1
port: 443
tcp_8080:
name: ''
protocol: tcp
listenAddress: ''
port: 8080
<<: *httpHandlers
tcp_8443:
name: ''
protocol: tcp
listenAddress: ''
port: 8443
<<: *httpHandlers
tcp_9110:
name: ''
protocol: tcp
listenAddress: ''
port: 9110
endpoints:
metrics:
handler: metrics_exporter
options:
route: /metrics

View file

@ -1,166 +0,0 @@
x-response-rules: &httpResponseRules
rules:
- pattern: ".*\\.(?i)exe"
matcher: Path
- pattern: "^application/octet-stream$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/sample.exe
- pattern: "^image/jpeg$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/default.jpg
- pattern: ".*\\.(?i)(jpg|jpeg)"
matcher: Path
response: ./assets/fakeFiles/default.jpg
- pattern: "^image/png$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/default.png
- pattern: ".*\\.(?i)png"
matcher: Path
response: ./assets/fakeFiles/default.png
- pattern: ".*\\.(?i)gif"
matcher: Path
response: ./assets/fakeFiles/default.gif
- pattern: ".*\\.(?i)ico"
matcher: Path
response: ./assets/fakeFiles/default.ico
- pattern: "^text/plain$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/default.txt
- pattern: ".*\\.(?i)txt"
matcher: Path
response: ./assets/fakeFiles/default.txt
- pattern: "^text/html$"
target: Accept
matcher: Header
response: ./assets/fakeFiles/default.html
- pattern: ".*"
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
tls:
curve: P256
minTLSVersion: SSL3
includeInsecureCipherSuites: false
validity:
ca:
notBeforeRelative: 17520h
notAfterRelative: 17520h
server:
NotBeforeRelative: 168h
NotAfterRelative: 168h
rootCaCert:
publicKeyPath: ./assets/demoCA/ca.pem
privateKeyPath: ./assets/demoCA/ca.key
certCachePath: /tmp/inetmock/
listeners:
udp_53:
name: ''
protocol: udp
listenAddress: ''
port: 1053
endpoints:
plainDns:
handler: dns_mock
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_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: 80
proxyTls:
handler: http_proxy
tls: true
options:
target:
ipAddress: 127.0.0.1
port: 443
tcp_8080:
name: ''
protocol: tcp
listenAddress: ''
port: 8080
<<: *httpHandlers
tcp_8443:
name: ''
protocol: tcp
listenAddress: ''
port: 8443
<<: *httpHandlers
tcp_9110:
name: ''
protocol: tcp
listenAddress: ''
port: 9110
endpoints:
metrics:
handler: metrics_exporter
options:
route: /metrics

View file

@ -1 +0,0 @@
OPTIONS="--config=/etc/inetmock/config.yaml"

View file

@ -1,15 +0,0 @@
[Unit]
Description=INetMock is a simple service to simulate a valid internet connection
[Service]
Type=simple
User=inetmock
AmbientCapabilities=CAP_NET_BIND_SERVICE
MemoryMax=50M
CPUQuota=20%
EnvironmentFile=/etc/default/inetmock
ExecStart=/usr/bin/inetmock $OPTIONS
WorkingDirectory=/var/lib/inetmock
[Install]
WantedBy=multi-user.target

1
docs/.gitignore vendored
View file

@ -1 +0,0 @@
book

View file

@ -1,15 +0,0 @@
.PHONY: book deploy
book:
@mdbook build
deploy: book
@rm -rf /tmp/book
@git worktree remove -f /tmp/book
@git worktree add /tmp/book gh-pages
@rm -rf /tmp/book/*
@cp -rp book/* /tmp/book/
@cd /tmp/book && \
git add -A && \
git commit -m "deployed on $(shell date) by ${USER}" && \
git push origin gh-pages

View file

@ -1,10 +0,0 @@
[book]
authors = ["Peter Kurfer"]
language = "en"
multilingual = false
src = "src"
title = "INetMock docs"
[build]
build-dir = "book"
create-missing = true

View file

@ -1,13 +0,0 @@
# Summary
- [Building & Installation](build_install.md)
- [Configuration](config.md)
- [`config.yaml`](config/yaml-config.md)
- [`http_mock`](config/http_mock.md)
- [`dns_mock`](config/dns_mock.md)
- [`tls_interceptor`](config/tls_interceptor.md)
- [Deployment](deploy.md)
- [API](api.md)
- [Custom handler](dev/custom_handler.md)
- [Plugin command](dev/plugin_command.md)
- [Logging](dev/logging.md)

View file

@ -1 +0,0 @@
# API

View file

@ -1,29 +0,0 @@
# Installation
## Building from source
### Requirements
* go 1.14
* make
* gcc
### Binary
To get the binary and all plugins just run
```bash
make
```
and you'll get a `inetmock` binary and a `plugins` directory containing all default plugins.
The default plugins are:
* `http_mock`
* `dns_mock`
* `tls_interceptor`
## Docker/Podman
## Getting a pre-built binary (coming soon)

View file

@ -1,21 +0,0 @@
# Configuration
## Plugins & handlers
_INetMock_ is based on plugins that ship one or more __protocol handlers__. Examples for protocol handlers are HTTP or
DNS but also TLS.
The application ships with the following handlers:
* `http_mock`
* `dns_mock`
* `tls_interceptor`
The configuration of an so called endpoint always specifies which handler should be used, which IP address and port it
should listen on and some handler specific `options`. This way the whole system is very flexible and can be configured
for various individual scenarios.
## Commands
Beside of __protocol handlers__ a plugin can also ship custom commands e.g. the `tls_interceptor` ships a `generate-ca`
command to bootstrap a certificate authority key-pair that can be reused for multiple instances.

View file

@ -1,142 +0,0 @@
# `dns_mock`
## Intro
The `dns_mock` handler expects an array of rules how it should respond to dfferent DNS queries and a fallback strategy.
Currently only queries for __A__ records are supported. The rules are primarily meant to define some exceptions or well
known DNS responses e.g. to return to right Google DNS IP but for everything else it will return dummy IPs.
The rules for the `dns_mock` handler are equivalent to the `http_mock` rules. Every rule consists of a `pattern` that
specifies a query name e.g. a single host, a wildcard domain, a wildcard top-level domain or even a _"match all"_ rule
is possible. These rules are evaluated in the same order they are defined in the `config.yaml`.
The fallback strategy is taken into account whenever a query does not match a rule.
Right now the following fallback strategies are available:
* _random_
* _incremental_
Just like the handler is configured via the `options` object the fallback strategies are configured via an `args`
object.
### _random_ fallback
The _random_ fallback strategy is the easier one of the both. It doesn't take any argument and it just shuffles a random
IP address for every request no matter if it was already asked for this IP or not.
### _incremental_ fallback
The _incremental_ fallback is little bit more intelligent. It takes a `startIP` as an argument which defines from which
IP address the strategy starts counting up to respond to DNS queries. Just like the _incremental_ strategy it is _
stateless_ and does not store any already given response for later reuse (at least for now).
## Configuration
### Matching an explicit host
The easiest possible pattern is to match a single host:
```yml
endpoints:
plainDns:
handler: dns_mock
listenAddress: 0.0.0.0
port: 53
options:
rules:
- pattern: "github\\.com"
response: 1.1.1.1
```
### Matching a whole domain
While matching a single host is nice2have it's not very helpful in most cases - except for some edge cases where it
might be necesary to specifically return a certain IP address. But it's also possible to match a whole domain no matter
what subdomain or sub-subdomain or whatever is requested like this:
```yml
endpoints:
plainDns:
handler: dns_mock
listenAddress: 0.0.0.0
port: 53
options:
rules:
- pattern: ".*\\.google\\.com"
response: 2.2.2.2
```
### Matching a whole TLD
In some cases it might also be interesting to distinguish between different requested TLDs. Therefore it might be
interesting to define one IP address to resolve to for every TLD that should be distinguishable.
```yml
endpoints:
plainDns:
handler: dns_mock
listenAddress: 0.0.0.0
port: 53
options:
rules:
- pattern: ".*\\.com"
response: 2.2.2.2
```
### Matching any query
Last but not least it is obvously also possible to match any query. This is comparable to a _"static"_ fallback strategy
in cases where different IP addresses are not necessary but the network setup should be as easy as possible.
```yml
endpoints:
plainDns:
handler: dns_mock
listenAddress: 0.0.0.0
port: 53
options:
rules:
- pattern: ".*"
response: 10.0.10.1
```
### Fallback strategies
#### _random_
Like previously mentioned the _random_ strategy is easy as it can be. It just takes a random unsigned integer of 32
bits, converts it to an IP address and returns this address as response. Therefore no further configuration is necessary
for now.
```yml
endpoints:
plainDns:
handler: dns_mock
listenAddress: 0.0.0.0
port: 53
options:
rules: []
fallback:
strategy: random
```
#### _incremental_
Also like previously mentioned the _incremental_ fallback strategy is fairly easy to setup. It just takes a `startIP` as
argument which is used to count upwards. It does __not__ check for an interval or something like this right now so a
overflow might occur.
```yml
endpoints:
plainDns:
handler: dns_mock
listenAddress: 0.0.0.0
port: 53
options:
rules: []
fallback:
strategy: incremental
args:
startIP: 10.0.0.0
```

View file

@ -1,88 +0,0 @@
# `http_mock`
## Intro
The `http_mock` handler expects an array of rules how it should respond to different request paths. This allows to e.g.
return an image if the request path contains something like _"asdf.jpg"_ but with binary if the request path contains
something like _"malicous.exe"_.
A _"catch all"_ rule could return in any case an HTML page or if nothing is provided the handler returns an HTTP 404
status code.
The rules are taken into account in the same order than they are defined in the `config.yaml`.
Every rule consists of a regex `pattern` (__re2__ compatible) and a `response` path to the file it should return.
In the future more advanced rules might be possible e.g. to match not on the request path but on some header values.
## Configuration
### Matching a specific path
The easiest possible pattern is to match a static request path:
```yml
endpoints:
plainHttp:
handler: http_mock
listenAddress: 0.0.0.0
port: 80
options:
rules:
- pattern: "/static/http/path/sample.exe"
response: ./assets/fakeFiles/sample.exe
```
### Matching a file extensions
While matching a static path might be nice as an example it's not very useful. Returning a given file for all kinds of
of request paths based on the requested file extension is way more handy:
```yml
endpoints:
plainHttp:
handler: http_mock
listenAddress: 0.0.0.0
port: 80
options:
rules:
- pattern: ".*\\.png"
response: ./assets/fakeFiles/default.png
```
So this is already way more flexible but we can do even better:
```yml
endpoints:
plainHttp:
handler: http_mock
listenAddress: 0.0.0.0
port: 80
options:
rules:
- pattern: ".*\\.(?i)(jpg|jpeg)"
response: ./assets/fakeFiles/default.jpg
```
This way the extension ignores any case and matches both `.jpg` and `.jpeg` (and of course also e.g. `.JpEg` and so on
and so forth).
The default `config.yaml` already ships with some basic rules to handle the most common file extensions.
### Defining a fallback
Last but not least a default case might be necessary to get at least any response but a 404.
This can be achieved with a `.*` pattern that literally matches everything:
```yml
endpoints:
plainHttp:
handler: http_mock
listenAddress: 0.0.0.0
port: 80
options:
rules:
- pattern: ".*"
response: ./assets/fakeFiles/default.html
```

View file

@ -1 +0,0 @@
# `tls_interceptor`

View file

@ -1,31 +0,0 @@
# `config.yaml`
## Intro
The configuration of _INetMock_ is mostly done in the `config.yaml`. It defines which endpoints should be started with
which handler and a few more things.
Every endpoint has a name that is used for logging and as already mentioned consists of listening IP and port, the
handler and its options.
INetMock comes with _"Batteries included"_ and ships with a basic `config.yaml` that defines a basic set of endpoints
for:
* HTTP
* HTTPS
* DNS
* DNS-over-TLS
## Sample
```yml
endpoints:
myHttpEndpoint:
handler: http_mock
listenAddress: 127.0.0.1
port: 8080
options:
rules:
- pattern: ".*"
target: ./assets/fakeFiles/default.html
```

View file

@ -1 +0,0 @@
# Deployment

View file

@ -1 +0,0 @@
# Custom handler

View file

@ -1 +0,0 @@
# Logging

View file

@ -1 +0,0 @@
# Plugin command

27
go.mod
View file

@ -1,27 +0,0 @@
module gitlab.com/inetmock/inetmock
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/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/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
google.golang.org/protobuf v1.25.0
gopkg.in/elazarl/goproxy.v1 v1.0.0-20180725130230-947c36da3153
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

710
go.sum
View file

@ -1,710 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
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=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
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/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=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
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=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
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-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.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=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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-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-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.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=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
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/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=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
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=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
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=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
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.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=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
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=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
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=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
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/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.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
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.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.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=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
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.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=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
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.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=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
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/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/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=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
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/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/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=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
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/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
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/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.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=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
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.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=
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=
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=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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-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/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=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
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/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=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/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-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/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=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
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=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4=
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-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=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
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.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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
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/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=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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-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=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

View file

@ -1,366 +0,0 @@
//go:generate mockgen -source=$GOFILE -destination=./mock/app.mock.go -package=mock
package app
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/audit/sink"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/health"
"gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/path"
"go.uber.org/zap"
)
var (
configFilePath string
logLevel string
developmentLogs bool
)
type contextKey string
const (
loggerKey contextKey = "gitlab.com/inetmock/inetmock/app/context/logger"
configKey contextKey = "gitlab.com/inetmock/inetmock/app/context/config"
handlerRegistryKey contextKey = "gitlab.com/inetmock/inetmock/app/context/handlerRegistry"
healthCheckerKey contextKey = "gitlab.com/inetmock/inetmock/app/context/healthChecker"
endpointManagerKey contextKey = "gitlab.com/inetmock/inetmock/app/context/endpointManager"
certStoreKey contextKey = "gitlab.com/inetmock/inetmock/app/context/certStore"
eventStreamKey contextKey = "gitlab.com/inetmock/inetmock/app/context/eventStream"
)
type App interface {
EventStream() audit.EventStream
Config() Config
Checker() health.Checker
Logger() logging.Logger
EndpointManager() endpoint.Orchestrator
HandlerRegistry() endpoint.HandlerRegistry
Context() context.Context
RootCommand() *cobra.Command
MustRun()
Shutdown()
// WithCommands adds subcommands to the root command
// requires nothing
WithCommands(cmds ...*cobra.Command) App
// WithHandlerRegistry builds up the handler registry
// requires nothing
WithHandlerRegistry(registrations ...endpoint.Registration) App
// WithHealthChecker adds the health checker mechanism
// requires nothing
WithHealthChecker() App
// WithLogger configures the logging system
// requires nothing
WithLogger() App
// WithEndpointManager creates an endpoint manager instance and adds it to the context
// requires WithHandlerRegistry, WithHealthChecker and WithLogger
WithEndpointManager() App
// WithCertStore initializes the cert store
// requires WithLogger and WithConfig
WithCertStore() App
// WithEventStream adds the audit event stream
// requires WithLogger
WithEventStream() App
// WithConfig loads the config
// requires nothing
WithConfig() App
WithInitTasks(task ...func(cmd *cobra.Command, args []string) (err error)) App
}
type app struct {
rootCmd *cobra.Command
ctx context.Context
cancel context.CancelFunc
lateInitTasks []func(cmd *cobra.Command, args []string) (err error)
}
func (a *app) MustRun() {
if err := a.rootCmd.Execute(); err != nil {
if a.Logger() != nil {
a.Logger().Error(
"Failed to run inetmock",
zap.Error(err),
)
} else {
panic(err)
}
}
}
func (a *app) Logger() logging.Logger {
val := a.ctx.Value(loggerKey)
if val == nil {
return nil
}
return val.(logging.Logger)
}
func (a *app) Config() Config {
val := a.ctx.Value(configKey)
if val == nil {
return nil
}
return val.(Config)
}
func (a *app) CertStore() cert.Store {
val := a.ctx.Value(certStoreKey)
if val == nil {
return nil
}
return val.(cert.Store)
}
func (a *app) Checker() health.Checker {
val := a.ctx.Value(healthCheckerKey)
if val == nil {
return nil
}
return val.(health.Checker)
}
func (a *app) EndpointManager() endpoint.Orchestrator {
val := a.ctx.Value(endpointManagerKey)
if val == nil {
return nil
}
return val.(endpoint.Orchestrator)
}
func (a *app) Audit() audit.Emitter {
val := a.ctx.Value(eventStreamKey)
if val == nil {
return nil
}
return val.(audit.Emitter)
}
func (a *app) EventStream() audit.EventStream {
val := a.ctx.Value(eventStreamKey)
if val == nil {
return nil
}
return val.(audit.EventStream)
}
func (a *app) HandlerRegistry() endpoint.HandlerRegistry {
val := a.ctx.Value(handlerRegistryKey)
if val == nil {
return nil
}
return val.(endpoint.HandlerRegistry)
}
func (a *app) Context() context.Context {
return a.ctx
}
func (a *app) RootCommand() *cobra.Command {
return a.rootCmd
}
func (a *app) Shutdown() {
a.cancel()
}
// WithCommands adds subcommands to the root command
// requires nothing
func (a *app) WithCommands(cmds ...*cobra.Command) App {
a.rootCmd.AddCommand(cmds...)
return a
}
// WithHandlerRegistry builds up the handler registry
// requires nothing
func (a *app) WithHandlerRegistry(registrations ...endpoint.Registration) App {
registry := endpoint.NewHandlerRegistry()
for _, registration := range registrations {
if err := registration(registry); err != nil {
panic(err)
}
}
a.ctx = context.WithValue(a.ctx, handlerRegistryKey, registry)
return a
}
// WithHealthChecker adds the health checker mechanism
// requires nothing
func (a *app) WithHealthChecker() App {
checker := health.New()
a.ctx = context.WithValue(a.ctx, healthCheckerKey, checker)
return a
}
// WithLogger configures the logging system
// requires nothing
func (a *app) WithLogger() App {
a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, args []string) (err error) {
logging.ConfigureLogging(
logging.ParseLevel(logLevel),
developmentLogs,
map[string]interface{}{
"cwd": path.WorkingDirectory(),
"cmd": cmd.Name(),
"args": args,
},
)
var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil {
return
}
a.ctx = context.WithValue(a.ctx, loggerKey, logger)
return
})
return a
}
// WithEndpointManager creates an endpoint manager instance and adds it to the context
// requires WithHandlerRegistry, WithHealthChecker and WithLogger
func (a *app) WithEndpointManager() App {
a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) {
epMgr := endpoint.NewOrchestrator(
a.Context(),
a.CertStore(),
a.HandlerRegistry(),
a.Audit(),
a.Logger().Named("Orchestrator"),
)
a.ctx = context.WithValue(a.ctx, endpointManagerKey, epMgr)
return
})
return a
}
// WithCertStore initializes the cert store
// requires WithLogger and WithConfig
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().TLSConfig(),
a.Logger().Named("CertStore"),
); err != nil {
return
}
a.ctx = context.WithValue(a.ctx, certStoreKey, certStore)
return
})
return a
}
// WithEventStream adds the audit event stream
// requires WithLogger
func (a *app) WithEventStream() App {
a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) {
var eventStream audit.EventStream
eventStream, err = audit.NewEventStream(
a.Logger().Named("EventStream"),
audit.WithSinkBufferSize(10),
)
if err != nil {
return
}
if err = eventStream.RegisterSink(a.ctx, sink.NewLogSink(a.Logger().Named("LogSink"))); err != nil {
return
}
var metricSink audit.Sink
if metricSink, err = sink.NewMetricSink(); err != nil {
return
}
if err = eventStream.RegisterSink(a.ctx, metricSink); err != nil {
return
}
a.ctx = context.WithValue(a.ctx, eventStreamKey, eventStream)
return
})
return a
}
// WithConfig loads the config
// requires nothing
func (a *app) WithConfig() App {
a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, _ []string) (err error) {
cfg := CreateConfig()
if err = cfg.ReadConfig(configFilePath); err != nil {
return
}
a.ctx = context.WithValue(a.ctx, configKey, cfg)
return
})
return a
}
func (a *app) WithInitTasks(task ...func(cmd *cobra.Command, args []string) (err error)) App {
a.lateInitTasks = append(a.lateInitTasks, task...)
return a
}
func NewApp(name, short string) App {
ctx, cancel := initAppContext()
a := &app{
rootCmd: &cobra.Command{
Use: name,
Short: short,
},
ctx: ctx,
cancel: cancel,
}
a.rootCmd.PersistentFlags().StringVar(&configFilePath, "config", "", "Path to config file that should be used")
a.rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "logging level to use")
a.rootCmd.PersistentFlags().BoolVar(&developmentLogs, "development-logs", false, "Enable development mode logs")
a.rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) {
for _, initTask := range a.lateInitTasks {
if err = initTask(cmd, args); err != nil {
return
}
}
return
}
return a
}
func initAppContext() (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(context.Background())
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func() {
<-signals
cancel()
}()
return ctx, cancel
}

View file

@ -1,89 +0,0 @@
package app
import (
"strings"
"github.com/spf13/viper"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/cert"
"gitlab.com/inetmock/inetmock/pkg/path"
)
func CreateConfig() Config {
configInstance := &config{
cfg: viper.New(),
}
configInstance.cfg.SetConfigName("config")
configInstance.cfg.SetConfigType("yaml")
configInstance.cfg.AddConfigPath("/etc/inetmock/")
configInstance.cfg.AddConfigPath("$HOME/.inetmock")
configInstance.cfg.AddConfigPath(".")
configInstance.cfg.SetEnvPrefix("INetMock")
configInstance.cfg.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
configInstance.cfg.AutomaticEnv()
for k, v := range registeredDefaults {
configInstance.cfg.SetDefault(k, v)
}
for k, v := range registeredAliases {
configInstance.cfg.RegisterAlias(k, v)
}
return configInstance
}
type Config interface {
ReadConfig(configFilePath string) error
ReadConfigString(config, format string) error
TLSConfig() cert.CertOptions
APIConfig() RPC
ListenerSpecs() map[string]endpoint.ListenerSpec
}
type config struct {
cfg *viper.Viper
TLS cert.CertOptions
Listeners map[string]endpoint.ListenerSpec
API RPC
}
func (c *config) APIConfig() RPC {
return c.API
}
func (c *config) ReadConfigString(config, format string) (err error) {
c.cfg.SetConfigType(format)
if err = c.cfg.ReadConfig(strings.NewReader(config)); err != nil {
return
}
err = c.cfg.Unmarshal(c)
return
}
func (c config) ListenerSpecs() map[string]endpoint.ListenerSpec {
return c.Listeners
}
func (c config) TLSConfig() cert.CertOptions {
return c.TLS
}
func (c *config) ReadConfig(configFilePath string) (err error) {
if configFilePath != "" && path.FileExists(configFilePath) {
c.cfg.SetConfigFile(configFilePath)
}
if err = c.cfg.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
err = nil
} else {
return
}
}
err = c.cfg.Unmarshal(c)
return
}

View file

@ -1,70 +0,0 @@
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

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

View file

@ -1,15 +0,0 @@
package app
var (
registeredDefaults = make(map[string]interface{})
// default aliases
registeredAliases = map[string]string{}
)
func AddDefaultValue(key string, val interface{}) {
registeredDefaults[key] = val
}
func AddAlias(alias, orig string) {
registeredAliases[alias] = orig
}

View file

@ -1,15 +0,0 @@
package app
import "net/url"
type RPC struct {
Listen string
}
func (r RPC) ListenURL() (u *url.URL) {
var err error
if u, err = url.Parse(r.Listen); err != nil {
u, _ = url.Parse("tcp://:0")
}
return
}

View file

@ -1,71 +0,0 @@
package app
import (
"net/url"
"reflect"
"testing"
)
func TestRPC_ListenURL(t *testing.T) {
type fields struct {
Listen string
}
tests := []struct {
name string
fields fields
wantU *url.URL
}{
{
name: "Parse valid TCP URL",
fields: fields{
Listen: "tcp://localhost:8080",
},
wantU: func() *url.URL {
if u, e := url.Parse("tcp://localhost:8080"); e != nil {
t.Errorf("Error during URL parsing: %v", e)
return nil
} else {
return u
}
}(),
},
{
name: "Parse valid unix socket url",
fields: fields{
Listen: "unix:///var/run/inetmock.sock",
},
wantU: func() *url.URL {
if u, e := url.Parse("unix:///var/run/inetmock.sock"); e != nil {
t.Errorf("Error during URL parsing: %v", e)
return nil
} else {
return u
}
}(),
},
{
name: "Expect fallback value due to parse error",
fields: fields{
Listen: `"tcp;\\asdf:234sedf`,
},
wantU: func() *url.URL {
if u, e := url.Parse("tcp://:0"); e != nil {
t.Errorf("Error during URL parsing: %v", e)
return nil
} else {
return u
}
}(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := RPC{
Listen: tt.fields.Listen,
}
if gotU := r.ListenURL(); !reflect.DeepEqual(gotU, tt.wantU) {
t.Errorf("ListenURL() = %v, want %v", gotU, tt.wantU)
}
})
}
}

View file

@ -1,31 +0,0 @@
//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,7 +0,0 @@
package endpoint
import "time"
const (
shutdownTimeout = 5 * time.Second
)

View file

@ -1,42 +0,0 @@
package endpoint
import (
"context"
"time"
"go.uber.org/zap"
)
const (
startupTimeoutDuration = 100 * time.Millisecond
)
type Endpoint struct {
Spec
name string
uplink Uplink
}
func (e Endpoint) Start(lifecycle Lifecycle) (err error) {
startupResult := make(chan error)
ctx, cancel := context.WithTimeout(lifecycle.Context(), startupTimeoutDuration)
defer cancel()
go func() {
defer func() {
if r := recover(); r != nil {
lifecycle.Logger().Fatal("Startup error recovered", zap.Any("recovered", r))
}
}()
startupResult <- e.Handler.Start(lifecycle)
}()
select {
case err = <-startupResult:
case <-ctx.Done():
err = ErrStartupTimeout
}
return
}

View file

@ -1,83 +0,0 @@
package mock
import (
"encoding/binary"
"math"
"math/rand"
"net"
"unsafe"
"github.com/mitchellh/mapstructure"
)
const (
randomIPStrategyName = "random"
incrementalIPStrategyName = "incremental"
)
var (
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(args map[string]interface{}) ResolverFallback
func CreateResolverFallback(name string, args map[string]interface{}) ResolverFallback {
if factory, ok := fallbackStrategies[name]; ok {
return factory(args)
} else {
return fallbackStrategies[randomIPStrategyName](args)
}
}
type ResolverFallback interface {
GetIP() net.IP
}
type incrementalIPFallback struct {
latestIp uint32
}
func (i *incrementalIPFallback) GetIP() net.IP {
if i.latestIp < math.MaxInt32 {
i.latestIp += 1
}
return uint32ToIP(i.latestIp)
}
type randomIPFallback struct {
}
func (randomIPFallback) GetIP() net.IP {
return uint32ToIP(uint32(rand.Int31()))
}
func uint32ToIP(i uint32) net.IP {
bytes := (*[4]byte)(unsafe.Pointer(&i))[:]
return net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0])
}
func ipToInt32(ip net.IP) uint32 {
v4 := ip.To4()
result := binary.BigEndian.Uint32(v4)
return result
}

View file

@ -1,106 +0,0 @@
package mock
import (
"net"
"reflect"
"testing"
)
func Test_randomIPFallback_GetIP(t *testing.T) {
ra := randomIPFallback{}
for i := 0; i < 1000; i++ {
if got := ra.GetIP(); reflect.DeepEqual(got, net.IP{}) {
t.Errorf("GetIP() = %v", got)
}
}
}
func Test_incrementalIPFallback_GetIP(t *testing.T) {
type fields struct {
latestIp uint32
}
tests := []struct {
name string
fields fields
want []net.IP
}{
{
name: "Expect the next icremental IP",
fields: fields{
latestIp: 167772160,
},
want: []net.IP{
net.IPv4(10, 0, 0, 1),
},
},
{
name: "Expect a sequence of 5",
fields: fields{
latestIp: 167772160,
},
want: []net.IP{
net.IPv4(10, 0, 0, 1),
net.IPv4(10, 0, 0, 2),
net.IPv4(10, 0, 0, 3),
net.IPv4(10, 0, 0, 4),
net.IPv4(10, 0, 0, 5),
},
},
{
name: "Expect next block to be incremented",
fields: fields{
latestIp: 167772413,
},
want: []net.IP{
net.IPv4(10, 0, 0, 254),
net.IPv4(10, 0, 0, 255),
net.IPv4(10, 0, 1, 0),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := &incrementalIPFallback{
latestIp: tt.fields.latestIp,
}
for k := 0; k < len(tt.want); k++ {
if got := i.GetIP(); !reflect.DeepEqual(got, tt.want[k]) {
t.Errorf("GetIP() = %v, want %v", got, tt.want[k])
}
}
})
}
}
func Test_ipToInt32(t *testing.T) {
type args struct {
ip net.IP
}
tests := []struct {
name string
args args
want uint32
}{
{
name: "Convert 188.193.106.113 to int",
args: args{
ip: net.ParseIP("188.193.106.113"),
},
want: 3166792305,
},
{
name: "Convert 192.168.178.10 to int",
args: args{
ip: net.ParseIP("192.168.178.10"),
},
want: 3232281098,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ipToInt32(tt.args.ip); got != tt.want {
t.Errorf("ipToInt32() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -1,77 +0,0 @@
package mock
import (
"context"
"time"
"github.com/miekg/dns"
"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
}
func (d *dnsHandler) Start(lifecycle endpoint.Lifecycle) (err error) {
var options dnsOptions
if options, err = loadFromConfig(lifecycle); err != nil {
return
}
d.logger = lifecycle.Logger().With(
zap.String("handler_name", lifecycle.Name()),
zap.String("address", lifecycle.Uplink().Addr().String()),
)
handler := &regexHandler{
handlerName: lifecycle.Name(),
fallback: options.Fallback,
logger: lifecycle.Logger(),
auditEmitter: lifecycle.Audit(),
}
for _, rule := range options.Rules {
d.logger.Info(
"register DNS rule",
zap.String("pattern", rule.pattern.String()),
zap.String("response", rule.response.String()),
)
handler.AddRule(rule)
}
if lifecycle.Uplink().Listener != nil {
d.dnsServer = &dns.Server{
Listener: lifecycle.Uplink().Listener,
Handler: handler,
}
} else {
d.dnsServer = &dns.Server{
PacketConn: lifecycle.Uplink().PacketConn,
Handler: handler,
}
}
go d.startServer()
return
}
func (d *dnsHandler) startServer() {
if err := d.dnsServer.ActivateAndServe(); err != nil {
d.logger.Error(
"failed to start DNS server listener",
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))
}
}

View file

@ -1,77 +0,0 @@
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

@ -1,57 +0,0 @@
package mock
import (
"net"
"regexp"
"gitlab.com/inetmock/inetmock/internal/endpoint"
)
type resolverRule struct {
pattern *regexp.Regexp
response net.IP
}
type dnsOptions struct {
Rules []resolverRule
Fallback ResolverFallback
}
func loadFromConfig(lifecycle endpoint.Lifecycle) (options dnsOptions, err error) {
type rule struct {
Pattern string
Response string
}
type fallback struct {
Strategy string
Args map[string]interface{}
}
opts := struct {
Rules []rule
Fallback fallback
}{}
err = lifecycle.UnmarshalOptions(&opts)
for _, rule := range opts.Rules {
var err error
var rr resolverRule
if rr.pattern, err = regexp.Compile(rule.Pattern); err != nil {
continue
}
if rr.response = net.ParseIP(rule.Response); rr.response == nil {
continue
}
options.Rules = append(options.Rules, rr)
}
options.Fallback = CreateResolverFallback(
opts.Fallback.Strategy,
opts.Fallback.Args,
)
return
}

View file

@ -1,139 +0,0 @@
package mock
import (
"net"
"github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/audit/details"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
type regexHandler struct {
handlerName string
routes []resolverRule
fallback ResolverFallback
auditEmitter audit.Emitter
logger logging.Logger
}
func (rh *regexHandler) AddRule(rule resolverRule) {
rh.routes = append(rh.routes, rule)
}
func (rh *regexHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(rh.handlerName))
defer func() {
timer.ObserveDuration()
}()
rh.recordRequest(r, w.LocalAddr(), w.RemoteAddr())
m := new(dns.Msg)
m.Compress = false
m.SetReply(r)
if r.Opcode == dns.OpcodeQuery {
rh.handleQuery(m)
}
if err := w.WriteMsg(m); err != nil {
rh.logger.Error(
"Failed to write DNS response message",
zap.Error(err),
)
}
}
func (rh *regexHandler) handleQuery(m *dns.Msg) {
for _, q := range m.Question {
switch q.Qtype {
case dns.TypeA:
totalHandledRequestsCounter.WithLabelValues(rh.handlerName).Inc()
for _, rule := range rh.routes {
if !rule.pattern.MatchString(q.Name) {
continue
}
m.Authoritative = true
answer := &dns.A{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 60,
},
A: rule.response,
}
m.Answer = append(m.Answer, answer)
rh.logger.Info(
"matched DNS rule",
zap.String("pattern", rule.pattern.String()),
zap.String("response", rule.response.String()),
)
return
}
rh.handleFallbackForMessage(m, q)
default:
unhandledRequestsCounter.WithLabelValues(rh.handlerName).Inc()
rh.logger.Warn(
"Unhandled DNS question type - no response will be sent",
zap.Uint16("question_type", q.Qtype),
)
}
}
}
func (rh *regexHandler) handleFallbackForMessage(m *dns.Msg, q dns.Question) {
fallbackIP := rh.fallback.GetIP()
answer := &dns.A{
Hdr: dns.RR_Header{
Name: q.Name,
Rrtype: dns.TypeA,
Class: dns.ClassINET,
Ttl: 60,
},
A: fallbackIP,
}
rh.logger.Info(
"Falling back to generated IP",
zap.String("response", fallbackIP.String()),
)
m.Authoritative = true
m.Answer = append(m.Answer, answer)
}
func (rh *regexHandler) recordRequest(m *dns.Msg, localAddr, remoteAddr net.Addr) {
dnsDetails := &details.DNS{
OPCode: details.DNSOpCode(m.Opcode),
}
for _, q := range m.Question {
dnsDetails.Questions = append(dnsDetails.Questions, details.DNSQuestion{
RRType: details.ResourceRecordType(q.Qtype),
Name: q.Name,
})
}
ev := audit.Event{
Transport: guessTransportFromAddr(localAddr),
Application: audit.AppProtocol_DNS,
ProtocolDetails: dnsDetails,
}
ev.SetSourceIPFromAddr(remoteAddr)
ev.SetDestinationIPFromAddr(localAddr)
rh.auditEmitter.Emit(ev)
}
func guessTransportFromAddr(addr net.Addr) audit.TransportProtocol {
switch addr.(type) {
case *net.TCPAddr:
return audit.TransportProtocol_TCP
case *net.UDPAddr:
return audit.TransportProtocol_UDP
default:
return audit.TransportProtocol_UNKNOWN_TRANSPORT
}
}

View file

@ -1,54 +0,0 @@
package mock
import (
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/metrics"
)
const (
name = "dns_mock"
)
var (
handlerNameLblName = "handler_name"
totalHandledRequestsCounter *prometheus.CounterVec
unhandledRequestsCounter *prometheus.CounterVec
requestDurationHistogram *prometheus.HistogramVec
)
func AddDNSMock(registry endpoint.HandlerRegistry) (err error) {
if totalHandledRequestsCounter, err = metrics.Counter(
name,
"handled_requests_total",
"",
handlerNameLblName,
); err != nil {
return
}
if unhandledRequestsCounter, err = metrics.Counter(
name,
"unhandled_requests_total",
"",
handlerNameLblName,
); err != nil {
return
}
if requestDurationHistogram, err = metrics.Histogram(
name,
"request_duration",
"",
nil,
handlerNameLblName,
); err != nil {
return
}
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &dnsHandler{}
})
return
}

View file

@ -1,38 +0,0 @@
package http
import (
"crypto/tls"
"net/http"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/audit/details"
)
func EventFromRequest(request *http.Request, app audit.AppProtocol) audit.Event {
httpDetails := details.HTTP{
Method: request.Method,
Host: request.Host,
URI: request.RequestURI,
Proto: request.Proto,
Headers: request.Header,
}
ev := audit.Event{
Transport: audit.TransportProtocol_TCP,
Application: app,
ProtocolDetails: httpDetails,
}
if state, ok := tlsConnectionState(request.Context()); ok {
ev.TLS = &audit.TLSDetails{
Version: audit.TLSVersionToEntity(state.Version).String(),
CipherSuite: tls.CipherSuiteName(state.CipherSuite),
ServerName: state.ServerName,
}
}
ev.SetDestinationIPFromAddr(localAddr(request.Context()))
ev.SetSourceIPFromAddr(remoteAddr(request.Context()))
return ev
}

View file

@ -1,59 +0,0 @@
package http
import (
"context"
"crypto/tls"
"net"
"github.com/soheilhy/cmux"
)
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 {
return nil
}
return val.(net.Addr)
}
func remoteAddr(ctx context.Context) net.Addr {
val := ctx.Value(remoteAddrKey)
if val == nil {
return nil
}
return val.(net.Addr)
}

View file

@ -1,88 +0,0 @@
package mock
import (
"context"
"errors"
"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/logging"
"go.uber.org/zap"
)
const (
name = "http_mock"
handlerNameLblName = "handler_name"
ruleMatchedLblName = "rule_matched"
)
type httpHandler struct {
logger logging.Logger
server *http.Server
}
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(lifecycle); err != nil {
return
}
p.logger = p.logger.With(
zap.String("address", lifecycle.Uplink().Addr().String()),
)
router := &RegexpHandler{
logger: p.logger,
emitter: lifecycle.Audit(),
handlerName: lifecycle.Name(),
}
p.server = &http.Server{
Handler: router,
ConnContext: imHttp.StoreConnPropertiesInContext,
}
for _, rule := range options.Rules {
router.setupRoute(rule)
}
go p.startServer(lifecycle.Uplink().Listener)
go p.shutdownOnCancel(lifecycle.Context())
return
}
func (p *httpHandler) shutdownOnCancel(ctx context.Context) {
<-ctx.Done()
p.logger.Info("Shutting down HTTP mock")
if err := p.server.Close(); err != nil {
p.logger.Error(
"failed to shutdown HTTP server",
zap.Error(err),
)
}
return
}
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))
}
}()
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

@ -1,155 +0,0 @@
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,97 +0,0 @@
//go:generate go-enum -f $GOFILE --lower --marshal --names
package mock
import (
"net/http"
"path/filepath"
"regexp"
"gitlab.com/inetmock/inetmock/internal/endpoint"
)
var (
ruleValueSelectors = map[RequestMatchTarget]ruleValueSelector{
RequestMatchTargetHeader: func(req *http.Request, targetKey string) string {
return req.Header.Get(targetKey)
},
RequestMatchTargetPath: func(req *http.Request, _ string) string {
return req.URL.Path
},
}
)
/* ENUM(
Path,
Header
)
*/
type RequestMatchTarget int
func (x RequestMatchTarget) Matches(req *http.Request, targetKey string, regex *regexp.Regexp) bool {
val := ruleValueSelectors[x](req, targetKey)
return regex.MatchString(val)
}
type ruleValueSelector func(req *http.Request, targetKey string) string
type targetRule struct {
pattern *regexp.Regexp
response string
requestMatchTarget RequestMatchTarget
targetKey string
}
func (tr targetRule) Pattern() *regexp.Regexp {
return tr.pattern
}
func (tr targetRule) Response() string {
return tr.response
}
type httpOptions struct {
Rules []targetRule
}
func loadFromConfig(lifecycle endpoint.Lifecycle) (options httpOptions, err error) {
type tmpCfg struct {
Pattern string
Response string
Matcher string
Target string
}
tmpRules := struct {
Rules []tmpCfg
}{}
if err = lifecycle.UnmarshalOptions(&tmpRules); err != nil {
return
}
for _, i := range tmpRules.Rules {
var rulePattern *regexp.Regexp
var matchTargetValue RequestMatchTarget
var absoluteResponsePath string
var parseErr error
if rulePattern, parseErr = regexp.Compile(i.Pattern); parseErr != nil {
continue
}
if matchTargetValue, parseErr = ParseRequestMatchTarget(i.Matcher); parseErr != nil {
matchTargetValue = RequestMatchTargetPath
}
if absoluteResponsePath, parseErr = filepath.Abs(i.Response); parseErr != nil {
continue
}
options.Rules = append(options.Rules, targetRule{
pattern: rulePattern,
response: absoluteResponsePath,
requestMatchTarget: matchTargetValue,
targetKey: i.Target,
})
}
return
}

View file

@ -1,177 +0,0 @@
package mock
import (
"path/filepath"
"reflect"
"regexp"
"testing"
"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 map[string]interface{}
}
tests := []struct {
name string
args args
wantOptions httpOptions
wantErr bool
}{
{
name: "Parse default config",
args: args{
config: map[string]interface{}{
"rules": []struct {
Pattern string
Matcher string
Response string
}{
{
Pattern: ".*\\.(?i)exe",
Response: "./assets/fakeFiles/sample.exe",
},
},
},
},
wantOptions: httpOptions{
Rules: []targetRule{
{
pattern: regexp.MustCompile(".*\\.(?i)exe"),
response: func() string {
p, _ := filepath.Abs("./assets/fakeFiles/sample.exe")
return p
}(),
requestMatchTarget: RequestMatchTargetPath,
targetKey: "",
},
},
},
wantErr: false,
},
{
name: "Parse config with path matcher",
args: args{
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{
{
pattern: regexp.MustCompile(".*\\.(?i)exe"),
response: func() string {
p, _ := filepath.Abs("./assets/fakeFiles/sample.exe")
return p
}(),
requestMatchTarget: RequestMatchTargetPath,
targetKey: "",
},
},
},
wantErr: false,
},
{
name: "Parse config with header matcher",
args: args{
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{
{
pattern: regexp.MustCompile("^application/octet-stream$"),
response: func() string {
p, _ := filepath.Abs("./assets/fakeFiles/sample.exe")
return p
}(),
requestMatchTarget: RequestMatchTargetHeader,
targetKey: "Content-Type",
},
},
},
wantErr: false,
},
{
name: "Parse config with header matcher and TLS true",
args: args{
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{
Rules: []targetRule{
{
pattern: regexp.MustCompile("^application/octet-stream$"),
response: func() string {
p, _ := filepath.Abs("./assets/fakeFiles/sample.exe")
return p
}(),
requestMatchTarget: RequestMatchTargetHeader,
targetKey: "Content-Type",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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
}
if !reflect.DeepEqual(gotOptions, tt.wantOptions) {
t.Errorf("loadFromConfig() gotOptions = %v, want %v", gotOptions, tt.wantOptions)
}
})
}
}

View file

@ -1,68 +0,0 @@
package mock
import (
"net/http"
"strconv"
"github.com/prometheus/client_golang/prometheus"
imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
type route struct {
rule targetRule
handler http.Handler
}
type RegexpHandler struct {
handlerName string
logger logging.Logger
routes []*route
emitter audit.Emitter
}
func (h *RegexpHandler) Handler(rule targetRule, handler http.Handler) {
h.routes = append(h.routes, &route{rule, handler})
}
func (h *RegexpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(h.handlerName))
defer timer.ObserveDuration()
for idx := range h.routes {
rule := h.routes[idx].rule
if h.routes[idx].rule.requestMatchTarget.Matches(r, rule.targetKey, rule.pattern) {
totalRequestCounter.WithLabelValues(h.handlerName, strconv.FormatBool(true)).Inc()
h.routes[idx].handler.ServeHTTP(w, r)
return
}
}
// no pattern matched; send 404 response
totalRequestCounter.WithLabelValues(h.handlerName, strconv.FormatBool(false)).Inc()
http.NotFound(w, r)
}
func (h *RegexpHandler) setupRoute(rule targetRule) {
h.logger.Info(
"setup routing",
zap.String("route", rule.Pattern().String()),
zap.String("response", rule.Response()),
)
h.Handler(rule, emittingFileHandler{
emitter: h.emitter,
targetPath: rule.response,
})
}
type emittingFileHandler struct {
emitter audit.Emitter
targetPath string
}
func (f emittingFileHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
f.emitter.Emit(imHttp.EventFromRequest(request, audit.AppProtocol_HTTP))
http.ServeFile(writer, request, f.targetPath)
}

View file

@ -1,44 +0,0 @@
package mock
import (
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/metrics"
)
var (
totalRequestCounter *prometheus.CounterVec
requestDurationHistogram *prometheus.HistogramVec
)
func AddHTTPMock(registry endpoint.HandlerRegistry) (err error) {
if totalRequestCounter == nil {
if totalRequestCounter, err = metrics.Counter(
name,
"total_requests",
"",
handlerNameLblName,
ruleMatchedLblName,
); err != nil {
return
}
}
if requestDurationHistogram == nil {
if requestDurationHistogram, err = metrics.Histogram(
name,
"request_duration",
"",
nil,
handlerNameLblName,
); err != nil {
return
}
}
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &httpHandler{}
})
return
}

View file

@ -1,88 +0,0 @@
package proxy
import (
"context"
"errors"
"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/logging"
"go.uber.org/zap"
"gopkg.in/elazarl/goproxy.v1"
)
const (
name = "http_proxy"
)
type httpProxy struct {
logger logging.Logger
proxy *goproxy.ProxyHttpServer
server *http.Server
}
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 = lifecycle.UnmarshalOptions(&opts); err != nil {
return
}
h.server = &http.Server{
Handler: h.proxy,
ConnContext: imHttp.StoreConnPropertiesInContext,
}
h.logger = h.logger.With(
zap.String("handler_name", lifecycle.Name()),
zap.String("address", lifecycle.Uplink().Addr().String()),
)
tlsConfig := lifecycle.CertStore().TLSConfig()
proxyHandler := &proxyHttpHandler{
handlerName: lifecycle.Name(),
options: opts,
logger: h.logger,
emitter: lifecycle.Audit(),
}
proxyHTTPSHandler := &proxyHttpsHandler{
options: opts,
tlsConfig: tlsConfig,
emitter: lifecycle.Audit(),
}
h.proxy.OnRequest().Do(proxyHandler)
h.proxy.OnRequest().HandleConnect(proxyHTTPSHandler)
go h.startProxy(lifecycle.Uplink().Listener)
go h.shutdownOnContextDone(lifecycle.Context())
return
}
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),
)
}
}
func (h *httpProxy) shutdownOnContextDone(ctx context.Context) {
<-ctx.Done()
var err error
h.logger.Info("Shutting down HTTP proxy")
if err = h.server.Close(); err != nil {
h.logger.Error(
"failed to shutdown proxy endpoint",
zap.Error(err),
)
}
return
}

View file

@ -1,167 +0,0 @@
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

@ -1,18 +0,0 @@
package proxy
import (
"fmt"
)
type redirectionTarget struct {
IPAddress string
Port uint16
}
func (rt redirectionTarget) host() string {
return fmt.Sprintf("%s:%d", rt.IPAddress, rt.Port)
}
type httpProxyOptions struct {
Target redirectionTarget
}

View file

@ -1,70 +0,0 @@
package proxy
import (
"crypto/tls"
"net/http"
"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"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
"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
logger logging.Logger
emitter audit.Emitter
}
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()
retReq = req
p.emitter.Emit(imHttp.EventFromRequest(req, audit.AppProtocol_HTTP_PROXY))
var err error
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),
)
return req, nil
}
return
}
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
}
originalRequest.URL.Host = targetHost
return
}

View file

@ -1,38 +0,0 @@
package proxy
import (
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/metrics"
"go.uber.org/zap"
"gopkg.in/elazarl/goproxy.v1"
)
var (
handlerNameLblName = "handler_name"
requestDurationHistogram *prometheus.HistogramVec
)
func AddHTTPProxy(registry endpoint.HandlerRegistry) (err error) {
var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil {
return
}
logger = logger.With(
zap.String("protocol_handler", name),
)
if requestDurationHistogram, err = metrics.Histogram(name, "request_duration", "", nil, handlerNameLblName); err != nil {
return
}
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &httpProxy{
logger: logger,
proxy: goproxy.NewProxyHttpServer(),
}
})
return
}

View file

@ -1,55 +0,0 @@
package metrics
import (
"errors"
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
const (
name = "metrics_exporter"
)
type metricsExporter struct {
logger logging.Logger
server *http.Server
}
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", lifecycle.Name()),
zap.String("address", lifecycle.Uplink().Addr().String()),
)
mux := http.NewServeMux()
mux.Handle(exporterOptions.Route, promhttp.Handler())
m.server = &http.Server{
Handler: mux,
}
go func() {
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
}

View file

@ -1,5 +0,0 @@
package metrics
type metricsExporterOptions struct {
Route string
}

View file

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

View file

@ -1,161 +0,0 @@
package interceptor
import (
"context"
"crypto/tls"
"net"
"sync"
"time"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
const (
name = "tls_interceptor"
)
type tlsInterceptor struct {
name string
options tlsOptions
logger logging.Logger
listener net.Listener
shutdownRequested bool
currentConnectionsCount *sync.WaitGroup
currentConnections map[uuid.UUID]*proxyConn
connectionsMutex *sync.Mutex
}
func (t *tlsInterceptor) Start(ctx endpoint.Lifecycle) (err error) {
t.name = ctx.Name()
if err = ctx.UnmarshalOptions(&t.options); err != nil {
return
}
t.logger = t.logger.With(
zap.String("handler_name", ctx.Name()),
zap.String("address", ctx.Uplink().Addr().String()),
zap.String("Target", t.options.Target.address()),
)
t.listener = tls.NewListener(ctx.Uplink().Listener, ctx.CertStore().TLSConfig())
go t.startListener()
go t.shutdownOnContextDone(ctx.Context())
return
}
func (t *tlsInterceptor) shutdownOnContextDone(ctx context.Context) {
<-ctx.Done()
t.logger.Info("Shutting down TLS interceptor")
t.shutdownRequested = true
done := make(chan struct{})
go func() {
t.currentConnectionsCount.Wait()
close(done)
}()
select {
case <-done:
return
case <-time.After(100 * time.Millisecond):
for _, proxyConn := range t.currentConnections {
if err := proxyConn.Close(); err != nil {
t.logger.Error(
"error while closing remaining proxy connections",
zap.Error(err),
)
}
}
return
}
}
func (t *tlsInterceptor) startListener() {
for !t.shutdownRequested {
conn, err := t.listener.Accept()
if err != nil {
t.logger.Error(
"error during accept",
zap.Error(err),
)
continue
}
handledRequestCounter.WithLabelValues(t.name).Inc()
openConnectionsGauge.WithLabelValues(t.name).Inc()
t.currentConnectionsCount.Add(1)
go t.proxyConn(conn)
}
}
func (t *tlsInterceptor) proxyConn(conn net.Conn) {
timer := prometheus.NewTimer(requestDurationHistogram.WithLabelValues(t.name))
defer func() {
_ = conn.Close()
t.currentConnectionsCount.Done()
openConnectionsGauge.WithLabelValues(t.name).Dec()
timer.ObserveDuration()
}()
rAddr, err := net.ResolveTCPAddr("tcp", t.options.Target.address())
if err != nil {
t.logger.Error(
"failed to resolve proxy Target",
zap.Error(err),
)
}
targetConn, err := net.DialTCP("tcp", nil, rAddr)
if err != nil {
t.logger.Error(
"failed to connect to proxy Target",
zap.Error(err),
)
return
}
defer targetConn.Close()
proxyCon := &proxyConn{
source: conn,
target: targetConn,
}
conUID := uuid.New()
t.storeConnection(conUID, proxyCon)
Pipe(conn, targetConn)
t.cleanConnection(conUID)
switch tlsConn := conn.(type) {
case *tls.Conn:
if tlsConn.Handshake() != nil {
t.logger.Error(
"error occurred during TLS handshake",
zap.Error(tlsConn.Handshake()),
)
}
}
t.logger.Info(
"connection closed",
zap.String("remoteAddr", conn.RemoteAddr().String()),
)
}
func (t *tlsInterceptor) storeConnection(connUUID uuid.UUID, conn *proxyConn) {
t.connectionsMutex.Lock()
defer t.connectionsMutex.Unlock()
t.currentConnections[connUUID] = conn
}
func (t *tlsInterceptor) cleanConnection(connUUID uuid.UUID) {
t.connectionsMutex.Lock()
defer t.connectionsMutex.Unlock()
if _, ok := t.currentConnections[connUUID]; ok {
delete(t.currentConnections, connUUID)
}
}

View file

@ -1,18 +0,0 @@
package interceptor
import (
"fmt"
)
type redirectionTarget struct {
IPAddress string
Port uint16
}
func (rt redirectionTarget) address() string {
return fmt.Sprintf("%s:%d", rt.IPAddress, rt.Port)
}
type tlsOptions struct {
Target redirectionTarget
}

View file

@ -1,59 +0,0 @@
package interceptor
import (
"net"
"sync"
)
var (
bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
)
func chanFromConn(conn net.Conn) chan []byte {
c := make(chan []byte)
go func() {
b := bufferPool.Get().([]byte)
for {
n, err := conn.Read(b)
if n > 0 {
res := bufferPool.Get().([]byte)
// Copy the buffer so it doesn't get changed while read by the recipient.
copy(res, b[:n])
c <- res[:n]
}
if err != nil {
c <- nil
break
}
}
}()
return c
}
func Pipe(conn1 net.Conn, conn2 net.Conn) {
chan1 := chanFromConn(conn1)
chan2 := chanFromConn(conn2)
for {
select {
case b1 := <-chan1:
if b1 == nil {
return
} else {
conn2.Write(b1)
}
case b2 := <-chan2:
if b2 == nil {
return
} else {
conn1.Write(b2)
}
}
}
}

View file

@ -1,22 +0,0 @@
package interceptor
import (
"fmt"
"net"
)
type proxyConn struct {
source net.Conn
target net.Conn
}
func (p *proxyConn) Close() error {
var err error
if targetErr := p.target.Close(); targetErr != nil {
err = fmt.Errorf("error while closing Target conn: %w", targetErr)
}
if sourceErr := p.source.Close(); sourceErr != nil {
err = fmt.Errorf("error while closing source conn: %w", err)
}
return err
}

View file

@ -1,49 +0,0 @@
package interceptor
import (
"sync"
"github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus"
"gitlab.com/inetmock/inetmock/internal/endpoint"
"gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/metrics"
"go.uber.org/zap"
)
var (
labelNames = []string{"handler_name"}
handledRequestCounter *prometheus.CounterVec
openConnectionsGauge *prometheus.GaugeVec
requestDurationHistogram *prometheus.HistogramVec
)
func AddTLSInterceptor(registry endpoint.HandlerRegistry) (err error) {
var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil {
panic(err)
}
logger = logger.With(
zap.String("protocol_handler", name),
)
if handledRequestCounter, err = metrics.Counter(name, "handled_requests", "", labelNames...); err != nil {
return
}
if openConnectionsGauge, err = metrics.Gauge(name, "open_connections", "", labelNames...); err != nil {
return
}
if requestDurationHistogram, err = metrics.Histogram(name, "request_duration", "", nil, labelNames...); err != nil {
}
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
return &tlsInterceptor{
logger: logger,
currentConnectionsCount: new(sync.WaitGroup),
currentConnections: make(map[uuid.UUID]*proxyConn),
connectionsMutex: &sync.Mutex{},
}
})
return
}

View file

@ -1,69 +0,0 @@
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

@ -1,143 +0,0 @@
//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

@ -1,102 +0,0 @@
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

@ -1,47 +0,0 @@
//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

@ -1,33 +0,0 @@
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,47 +0,0 @@
package format
import (
"encoding/json"
"io"
"strings"
"github.com/olekukonko/tablewriter"
"gopkg.in/yaml.v3"
)
type consoleWriterFactory func(io.Writer) ConsoleWriter
var (
writers = map[string]consoleWriterFactory{
"table": func(writer io.Writer) ConsoleWriter {
tw := tablewriter.NewWriter(writer)
tw.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
tw.SetCenterSeparator("|")
return &tblWriter{
tableWriter: tw,
}
},
"json": func(writer io.Writer) ConsoleWriter {
return &jsonWriter{
encoder: json.NewEncoder(writer),
}
},
"yaml": func(writer io.Writer) ConsoleWriter {
return &yamlWriter{
encoder: yaml.NewEncoder(writer),
}
},
}
)
func Writer(format string, writer io.Writer) ConsoleWriter {
if cw, ok := writers[strings.ToLower(format)]; ok {
return cw(writer)
}
return writers["table"](writer)
}
type ConsoleWriter interface {
Write(in interface{}) (err error)
}

View file

@ -1,13 +0,0 @@
package format
import (
"encoding/json"
)
type jsonWriter struct {
encoder *json.Encoder
}
func (j *jsonWriter) Write(in interface{}) error {
return j.encoder.Encode(in)
}

View file

@ -1,115 +0,0 @@
package format
import (
"errors"
"fmt"
"reflect"
"strconv"
"github.com/olekukonko/tablewriter"
)
type tblWriter struct {
tableWriter *tablewriter.Table
}
func (t *tblWriter) Write(in interface{}) (err error) {
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
var vt reflect.Type
var numberOfFields int
data := make([][]string, 0)
switch v.Kind() {
case reflect.Interface:
return errors.New("interface{} is not supported")
case reflect.Slice, reflect.Array:
length := v.Len()
if length < 1 {
return
}
vt = v.Index(0).Type()
if vt.Kind() == reflect.Ptr {
vt = vt.Elem()
}
if vt.Kind() != reflect.Struct {
return fmt.Errorf("element type of array %v is not supported", vt.Kind())
}
numberOfFields = vt.NumField()
for i := 0; i < length; i++ {
data = append(data, t.getData(v.Index(i), numberOfFields))
}
case reflect.Struct:
vt = v.Type()
numberOfFields = vt.NumField()
data = append(data, t.getData(v, numberOfFields))
}
t.tableWriter.SetHeader(headersForType(vt, numberOfFields))
t.tableWriter.AppendBulk(data)
t.tableWriter.Render()
t.tableWriter.ClearRows()
return err
}
func (t *tblWriter) getData(val reflect.Value, numberOfFields int) (data []string) {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
for i := 0; i < numberOfFields; i++ {
data = append(data, value(val.Field(i)))
}
return
}
func value(val reflect.Value) string {
if val.IsZero() {
return ""
}
if stringer, isStringer := val.Interface().(fmt.Stringer); isStringer {
return stringer.String()
}
switch val.Kind() {
case reflect.Ptr:
return value(val.Elem())
case reflect.Struct, reflect.Interface:
return "<not supported>"
case reflect.Bool:
return strconv.FormatBool(val.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(val.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(val.Uint(), 10)
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(val.Float(), 'f', 6, 64)
default:
return val.String()
}
}
func headersForType(t reflect.Type, numberOfFields int) (headers []string) {
for i := 0; i < numberOfFields; i++ {
field := t.Field(i)
if tableTag, ok := field.Tag.Lookup("table"); ok {
headers = append(headers, tableTag)
} else {
headers = append(headers, field.Name)
}
}
return
}

View file

@ -1,134 +0,0 @@
package format
import (
"strings"
"testing"
)
func Test_tblWriter_Write(t *testing.T) {
type s1 struct {
Name string
Age int
}
type s2 struct {
Name string `table:"Full name"`
Age int `table:"Age in years"`
}
type args struct {
in interface{}
}
tests := []struct {
name string
args args
wantErr bool
wantResult string
}{
{
name: "Test write table without errors",
args: args{
in: s1{
Name: "Ted Tester",
Age: 28,
},
},
wantErr: false,
wantResult: `
| NAME | AGE |
|------------|-----|
| Ted Tester | 28 |
`,
},
{
name: "Test write table without errors with pointer value",
args: args{
in: &s1{
Name: "Ted Tester",
Age: 28,
},
},
wantErr: false,
wantResult: `
| NAME | AGE |
|------------|-----|
| Ted Tester | 28 |
`,
},
{
name: "Test write table without errors with multiple rows",
args: args{
in: []s1{
{
Name: "Ted Tester",
Age: 28,
},
{
Name: "Heinz",
Age: 33,
},
},
},
wantErr: false,
wantResult: `
| NAME | AGE |
|------------|-----|
| Ted Tester | 28 |
| Heinz | 33 |
`,
},
{
name: "Test write table without errors with multiple pointer rows",
args: args{
in: []*s1{
{
Name: "Ted Tester",
Age: 28,
},
{
Name: "Heinz",
Age: 33,
},
},
},
wantErr: false,
wantResult: `
| NAME | AGE |
|------------|-----|
| Ted Tester | 28 |
| Heinz | 33 |
`,
},
{
name: "Test write table without errors and with custom headers",
args: args{
in: s2{
Name: "Ted Tester",
Age: 28,
},
},
wantErr: false,
wantResult: `
| FULL NAME | AGE IN YEARS |
|------------|--------------|
| Ted Tester | 28 |
`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
bldr := &strings.Builder{}
// hack to be able to format expected strings pretty
bldr.WriteRune('\n')
tw := Writer("table", bldr)
if err := tw.Write(tt.args.in); (err != nil) != tt.wantErr {
t.Errorf("Write() error = %v, wantErr %v", err, tt.wantErr)
return
}
if bldr.String() != tt.wantResult {
t.Errorf("Write() got = %s, want %s", bldr.String(), tt.wantResult)
}
})
}
}

View file

@ -1,13 +0,0 @@
package format
import (
"gopkg.in/yaml.v3"
)
type yamlWriter struct {
encoder *yaml.Encoder
}
func (y *yamlWriter) Write(in interface{}) (err error) {
return y.encoder.Encode(in)
}

View file

@ -1,74 +0,0 @@
package rpc
import (
"context"
"io"
"os"
"gitlab.com/inetmock/inetmock/pkg/audit"
"gitlab.com/inetmock/inetmock/pkg/audit/sink"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
)
type auditServer struct {
UnimplementedAuditServer
logger logging.Logger
eventStream audit.EventStream
}
func (a *auditServer) ListSinks(context.Context, *ListSinksRequest) (*ListSinksResponse, error) {
return &ListSinksResponse{
Sinks: a.eventStream.Sinks(),
}, nil
}
func (a *auditServer) WatchEvents(req *WatchEventsRequest, srv Audit_WatchEventsServer) (err error) {
a.logger.Info("watcher attached", zap.String("name", req.WatcherName))
err = a.eventStream.RegisterSink(srv.Context(), sink.NewGenericSink(req.WatcherName, func(ev audit.Event) {
if err = srv.Send(ev.ProtoMessage()); err != nil {
return
}
}))
if err != nil {
return
}
<-srv.Context().Done()
a.logger.Info("Watcher detached", zap.String("name", req.WatcherName))
return
}
func (a *auditServer) RegisterFileSink(_ context.Context, req *RegisterFileSinkRequest) (resp *RegisterFileSinkResponse, err error) {
var writer io.WriteCloser
var flags int
switch req.OpenMode {
case FileOpenMode_APPEND:
flags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
default:
flags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
}
var permissions = os.FileMode(req.Permissions)
if permissions == 0 {
permissions = 644
}
if writer, err = os.OpenFile(req.TargetPath, flags, permissions); err != nil {
return
}
if err = a.eventStream.RegisterSink(context.Background(), sink.NewWriterSink(req.TargetPath, audit.NewEventWriter(writer))); err != nil {
return
}
resp = &RegisterFileSinkResponse{}
return
}
func (a *auditServer) RemoveFileSink(_ context.Context, req *RemoveFileSinkRequest) (*RemoveFileSinkResponse, error) {
gotRemoved := a.eventStream.RemoveSink(req.TargetPath)
return &RemoveFileSinkResponse{
SinkGotRemoved: gotRemoved,
}, nil
}

View file

@ -1,105 +0,0 @@
package rpc
import (
"net"
"net/url"
"os"
"time"
app2 "gitlab.com/inetmock/inetmock/internal/app"
"gitlab.com/inetmock/inetmock/pkg/logging"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
type INetMockAPI interface {
StartServer() error
StopServer()
}
type inetmockAPI struct {
app app2.App
url *url.URL
server *grpc.Server
logger logging.Logger
serverRunning bool
}
func NewINetMockAPI(
app app2.App,
) INetMockAPI {
return &inetmockAPI{
app: app,
url: app.Config().APIConfig().ListenURL(),
logger: app.Logger().Named("api"),
}
}
func (i *inetmockAPI) StartServer() (err error) {
var lis net.Listener
if lis, err = createListenerFromURL(i.url); err != nil {
return
}
i.server = grpc.NewServer()
RegisterHealthServer(i.server, &healthServer{
app: i.app,
})
RegisterAuditServer(i.server, &auditServer{
logger: i.app.Logger(),
eventStream: i.app.EventStream(),
})
reflection.Register(i.server)
go i.startServerAsync(lis)
return
}
func (i *inetmockAPI) StopServer() {
if !i.serverRunning {
i.logger.Info(
"Skipping API server shutdown because server is not running",
)
return
}
gracefulStopChan := make(chan struct{})
go func() {
i.server.GracefulStop()
close(gracefulStopChan)
}()
select {
case <-gracefulStopChan:
case <-time.After(5 * time.Second):
i.server.Stop()
}
}
func (i *inetmockAPI) startServerAsync(listener net.Listener) {
i.serverRunning = true
if err := i.server.Serve(listener); err != nil {
i.serverRunning = false
i.logger.Error(
"failed to start INetMock API",
zap.Error(err),
)
}
}
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)
}
return
}

Some files were not shown because too many files have changed in this diff Show more