├── tests └── .keep ├── docs ├── mocks.md ├── images │ ├── comparison.png │ └── grpc-wiremock.png ├── comparsion.md └── benchmarks.md ├── dev ├── example │ ├── .gitignore │ ├── Dockerfile │ ├── deps │ │ └── services │ │ │ └── push-sender │ │ │ └── grpc │ │ │ └── push-sender.proto │ ├── api │ │ └── grpc │ │ │ └── wearable.proto │ ├── go.mod │ ├── test │ │ └── wiremock │ │ │ └── push-sender │ │ │ └── mappings │ │ │ └── post_push_sender_notify_200.json │ ├── Makefile │ ├── internal │ │ └── wearable_service │ │ │ ├── service.go │ │ │ └── beats_per_minute.go │ └── certs │ │ ├── mockCA.crt │ │ └── mockCA.key ├── watch_wiremock.sh ├── init_wiremock.sh ├── watch_build_image.sh └── build_image.sh ├── static ├── proxy │ ├── template │ │ └── layout │ │ │ ├── cmd │ │ │ └── .keep │ │ │ ├── pkg │ │ │ ├── getenv │ │ │ │ ├── const.go │ │ │ │ └── getenv.go │ │ │ ├── status │ │ │ │ └── status.go │ │ │ └── wiremock │ │ │ │ └── client_test.go │ │ │ ├── Makefile │ │ │ ├── go.mod.rename.me │ │ │ └── internal │ │ │ └── health │ │ │ └── health.go │ └── files │ │ ├── service.go.tpl │ │ ├── method-unary.go.tpl │ │ └── method-client-streaming.go.tpl ├── supervisord │ ├── default-config-path │ │ ├── mocks │ │ │ └── keep │ │ └── supervisord.conf │ └── files │ │ └── supervisord.conf.tpl ├── tests │ └── data │ │ ├── wiremock │ │ └── with-domains │ │ │ ├── awesome │ │ │ └── keep │ │ │ └── push-sender │ │ │ └── keep │ │ ├── static-examples │ │ ├── without-gopackage-with-local-imports │ │ │ └── api │ │ │ │ └── grpc │ │ │ │ ├── another.proto │ │ │ │ ├── import │ │ │ │ └── another.proto │ │ │ │ └── example.proto │ │ ├── with-local-imports │ │ │ └── api │ │ │ │ └── grpc │ │ │ │ ├── import │ │ │ │ └── custom.proto │ │ │ │ └── example.proto │ │ ├── with-local-imports-as-reciever │ │ │ └── api │ │ │ │ └── grpc │ │ │ │ ├── local │ │ │ │ └── custom.proto │ │ │ │ └── example.proto │ │ ├── with-local-imports-as-reciever-and-golang-keywords │ │ │ └── api │ │ │ │ └── grpc │ │ │ │ ├── go │ │ │ │ └── custom.proto │ │ │ │ ├── fallthrough │ │ │ │ └── fallthrough.proto │ │ │ │ └── example.proto │ │ ├── without-methods │ │ │ └── api │ │ │ │ └── grpc │ │ │ │ └── example.proto │ │ ├── without-proto-files │ │ │ └── api │ │ │ │ └── grpc │ │ │ │ └── example.txt │ │ ├── without-gopackage │ │ │ └── api │ │ │ │ └── grpc │ │ │ │ └── example.proto │ │ ├── simple │ │ │ └── api │ │ │ │ └── grpc │ │ │ │ └── example.proto │ │ ├── with-common-imports │ │ │ └── api │ │ │ │ └── grpc │ │ │ │ └── example.proto │ │ └── two-service │ │ │ └── api │ │ │ └── grpc │ │ │ └── example.proto │ │ ├── supervisord │ │ ├── empty │ │ │ └── supervisord.conf │ │ ├── one-service │ │ │ ├── supervisord.conf │ │ │ └── mocks │ │ │ │ └── mock-awesome.conf │ │ ├── two-services │ │ │ ├── supervisord.conf │ │ │ └── mocks │ │ │ │ ├── mock-awesome.conf │ │ │ │ └── mock-push-sender.conf │ │ ├── with-includes │ │ │ ├── supervisord.conf │ │ │ └── mocks │ │ │ │ └── mock-awesome.conf │ │ └── simple │ │ │ └── supervisord.conf │ │ └── openapi │ │ ├── petstore │ │ ├── schemas │ │ │ ├── pet.yml │ │ │ └── error.yml │ │ ├── petstore.yaml │ │ └── users.yaml │ │ ├── users-merged │ │ └── users.yaml │ │ ├── petstore-and-users-merged │ │ ├── users.yaml │ │ └── petstore.yaml │ │ └── petstore-merged │ │ └── petstore.yaml ├── static.go ├── tests-statuses.yml ├── proxy-nginx │ └── files │ │ └── nginx.conf.tpl ├── health │ └── health.proto └── proto-annotations │ └── google │ └── api │ └── annotations.proto ├── .gitignore ├── .dockerignore ├── NOTICE ├── benchmarks ├── output │ ├── comparison.png │ ├── report.json │ ├── api_count_1 │ │ └── report_8000.txt │ ├── api_count_10 │ │ ├── report_8000.txt │ │ ├── report_8001.txt │ │ ├── report_8002.txt │ │ ├── report_8003.txt │ │ ├── report_8004.txt │ │ ├── report_8005.txt │ │ ├── report_8006.txt │ │ ├── report_8007.txt │ │ ├── report_8008.txt │ │ └── report_8009.txt │ ├── api_count_2 │ │ ├── report_8000.txt │ │ └── report_8001.txt │ ├── api_count_20 │ │ ├── report_8000.txt │ │ ├── report_8001.txt │ │ ├── report_8002.txt │ │ ├── report_8003.txt │ │ ├── report_8004.txt │ │ ├── report_8005.txt │ │ ├── report_8006.txt │ │ ├── report_8007.txt │ │ ├── report_8008.txt │ │ ├── report_8009.txt │ │ ├── report_8010.txt │ │ ├── report_8011.txt │ │ ├── report_8012.txt │ │ ├── report_8013.txt │ │ ├── report_8014.txt │ │ ├── report_8015.txt │ │ ├── report_8016.txt │ │ ├── report_8017.txt │ │ ├── report_8018.txt │ │ └── report_8019.txt │ └── api_count_5 │ │ ├── report_8000.txt │ │ ├── report_8001.txt │ │ ├── report_8002.txt │ │ ├── report_8003.txt │ │ └── report_8004.txt ├── scripts │ ├── entrypoint.sh │ ├── health.sh │ ├── run.sh │ ├── benchmark.sh │ └── report.sh ├── wiremock │ └── mappings │ │ └── test_mock_200.json ├── Makefile ├── Dockerfile-wiremock ├── Dockerfile-benchmarks └── docker-compose.yaml ├── scripts ├── proxy │ ├── run.sh │ ├── init.sh │ ├── watch.sh │ └── install.sh ├── multiapi │ └── init.sh ├── routing │ ├── logs.sh │ └── init.sh ├── run_wiremock.sh ├── other │ ├── log.sh │ └── check_dependencies.sh ├── mocks │ └── init.sh ├── entrypoint.sh ├── init.sh └── env.sh ├── pkg ├── compiler │ ├── compilecontract │ │ └── contract.go │ └── const.go ├── timespec │ ├── time_linux.go │ └── time_darwin.go ├── fstesting │ ├── entry │ │ └── entry.go │ ├── entrymock │ │ └── entry_mock.go │ └── setup.go ├── utils │ ├── executils │ │ ├── look.go │ │ └── fakecmd.go │ ├── sliceutils │ │ ├── sliceutils.go │ │ └── sliceutils_test.go │ ├── strutils │ │ ├── golang.go │ │ └── strutils.go │ ├── testutils │ │ └── testing_test.go │ └── httputils │ │ └── request.go ├── models │ ├── mock │ │ └── mock.go │ ├── basecontract │ │ ├── from_openapi.go │ │ ├── traverser │ │ │ └── traverse.go │ │ ├── loader │ │ │ ├── proto │ │ │ │ └── proto.go │ │ │ └── openapi │ │ │ │ └── openapi.go │ │ ├── unifier │ │ │ ├── openapi │ │ │ │ └── openapi.go │ │ │ └── unify.go │ │ └── from_proto.go │ └── protocontract │ │ ├── traverser │ │ ├── traverse.go │ │ └── traverse_test.go │ │ ├── loader │ │ └── proto.go │ │ └── contract_test.go ├── generators │ ├── proxy │ │ ├── const.go │ │ └── project.go │ ├── configs │ │ ├── nginx │ │ │ ├── reload.go │ │ │ └── configure.go │ │ ├── supervisord │ │ │ ├── reload.go │ │ │ └── configure.go │ │ └── generate.go │ ├── mocks │ │ └── mocks_test.go │ └── certificates │ │ ├── test │ │ └── tester.go │ │ ├── domains.go │ │ └── cert_test.go ├── renderer │ ├── funcs.go │ ├── resolver.go │ └── renderer.go ├── jsonschema │ ├── oapimock.go │ ├── datagen │ │ └── datagen.go │ └── schematomock │ │ └── statuses_test.go ├── printer │ └── print.go ├── wiremock │ ├── config │ │ ├── ports_test.go │ │ ├── config.go │ │ └── ports.go │ ├── client │ │ ├── wiremock.go │ │ ├── mocks.go │ │ └── model.go │ ├── configsync │ │ └── sync.go │ └── configopener │ │ └── open.go ├── errgroup │ └── errgroup.go ├── runner │ ├── errors.go │ ├── runner.go │ └── runner_test.go ├── blacklist │ ├── blacklist_test.go │ └── blacklist.go ├── watcher │ └── notifier.go ├── builder │ └── update.go └── svctesting │ └── svcrunner │ └── runner.go ├── cmd ├── reload │ ├── commands │ │ ├── commands.go │ │ └── reload.go │ ├── Makefile │ └── main.go ├── watcher │ ├── commands │ │ ├── commands.go │ │ └── watch.go │ ├── Makefile │ └── main.go ├── certgen │ ├── commands │ │ ├── commands.go │ │ └── certgen.go │ ├── Makefile │ └── main.go ├── confgen │ ├── commands │ │ └── commands.go │ ├── Makefile │ └── main.go ├── mockgen │ ├── commands │ │ ├── commands.go │ │ └── mockgen-first-run.go │ ├── Makefile │ └── main.go └── grpc2http │ ├── Makefile │ └── main.go ├── example ├── Makefile ├── cmd │ └── main.go └── go.mod ├── .image-build-args ├── etc ├── rsyslog.d │ └── wiremock.conf ├── supervisord │ └── supervisord.conf └── nginx │ └── http.d │ └── default.conf ├── COPYRIGHT ├── Makefile └── internal └── usecases ├── certgen └── generate.go ├── watcher ├── setup_test.go └── watch.go └── confgen ├── mocks └── generate_mock.go └── options.go /tests/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/mocks.md: -------------------------------------------------------------------------------- 1 | ## Mocks -------------------------------------------------------------------------------- /dev/example/.gitignore: -------------------------------------------------------------------------------- 1 | build/.env -------------------------------------------------------------------------------- /static/proxy/template/layout/cmd/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bash_history 2 | /tests 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | dev 4 | benchmarks 5 | -------------------------------------------------------------------------------- /static/supervisord/default-config-path/mocks/keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/tests/data/wiremock/with-domains/awesome/keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/tests/data/wiremock/with-domains/push-sender/keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This product includes software developed at 2 | The Apache Software Foundation (http://www.apache.org/). -------------------------------------------------------------------------------- /docs/images/comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuper-Tech/grpc-wiremock/HEAD/docs/images/comparison.png -------------------------------------------------------------------------------- /docs/images/grpc-wiremock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuper-Tech/grpc-wiremock/HEAD/docs/images/grpc-wiremock.png -------------------------------------------------------------------------------- /static/proxy/template/layout/pkg/getenv/const.go: -------------------------------------------------------------------------------- 1 | package getenv 2 | 3 | const ( 4 | defaultPort = "3010" 5 | ) 6 | -------------------------------------------------------------------------------- /benchmarks/output/comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kuper-Tech/grpc-wiremock/HEAD/benchmarks/output/comparison.png -------------------------------------------------------------------------------- /scripts/proxy/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | bash "${PROXY}/install.sh" 2>&1 | logger -t "${PROXY_GEN_HEADER}" -------------------------------------------------------------------------------- /scripts/proxy/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | bash "${PROXY}/watch.sh" 2>&1 | logger -t "${PROXY_WATCH_HEADER}" & -------------------------------------------------------------------------------- /pkg/compiler/compilecontract/contract.go: -------------------------------------------------------------------------------- 1 | package compilecontract 2 | 3 | type Contract struct { 4 | HeaderPath string 5 | 6 | ImportsPaths []string 7 | } 8 | -------------------------------------------------------------------------------- /pkg/timespec/time_linux.go: -------------------------------------------------------------------------------- 1 | package timespec 2 | 3 | import "syscall" 4 | 5 | func CreationTime(info *syscall.Stat_t) syscall.Timespec { 6 | return info.Ctim 7 | } 8 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/without-gopackage-with-local-imports/api/grpc/another.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | message Response {} 6 | -------------------------------------------------------------------------------- /benchmarks/scripts/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | WIREMOCK_API_COUNT="${WIREMOCK_API_COUNT-1}" 6 | 7 | bash /scripts/run.sh "${WIREMOCK_API_COUNT}" -------------------------------------------------------------------------------- /cmd/reload/commands/commands.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func CreateCommandRoot() *cobra.Command { 6 | return reloadCommand() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/watcher/commands/commands.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func CreateCommandRoot() *cobra.Command { 6 | return watchCommand() 7 | } 8 | -------------------------------------------------------------------------------- /pkg/timespec/time_darwin.go: -------------------------------------------------------------------------------- 1 | package timespec 2 | 3 | import "syscall" 4 | 5 | func CreationTime(info *syscall.Stat_t) syscall.Timespec { 6 | return info.Ctimespec 7 | } 8 | -------------------------------------------------------------------------------- /cmd/certgen/commands/commands.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func CreateCommandRoot() *cobra.Command { 6 | return certgenCommand() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/confgen/commands/commands.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func CreateCommandRoot() *cobra.Command { 6 | return confgenCommand() 7 | } 8 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/without-gopackage-with-local-imports/api/grpc/import/another.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package inner; 4 | 5 | message AnotherResponse {} 6 | -------------------------------------------------------------------------------- /scripts/multiapi/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | ETC_SUPERVISORD_PATH="/etc/supervisord" 6 | 7 | supervisord -d -c "${ETC_SUPERVISORD_PATH}/supervisord.conf" 8 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | PROXY_BINARY_NAME=grpc-to-http-proxy 2 | 3 | install: 4 | go mod tidy 5 | go build -o ${PROXY_BINARY_NAME} cmd/*.go 6 | mv ${PROXY_BINARY_NAME} ${GOBIN}/${PROXY_BINARY_NAME} 7 | -------------------------------------------------------------------------------- /scripts/routing/logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | sudo touch /var/log/nginx/{access,error,not_mocked}.log 6 | tail -f /var/log/nginx/* | logger -t "${ROUTING_NGINX_LOGS_HEADER}" 7 | -------------------------------------------------------------------------------- /static/tests/data/supervisord/empty/supervisord.conf: -------------------------------------------------------------------------------- 1 | [include] 2 | files = mocks/*.conf 3 | 4 | [program:mocks-watcher] 5 | environment = A="env 1",B="this is a test" 6 | command = watcher --mocks="hello" 7 | -------------------------------------------------------------------------------- /static/tests/data/supervisord/one-service/supervisord.conf: -------------------------------------------------------------------------------- 1 | [include] 2 | files = mocks/*.conf 3 | 4 | [program:mocks-watcher] 5 | environment = A="env 1",B="this is a test" 6 | command = watcher --mocks="hello" 7 | -------------------------------------------------------------------------------- /static/tests/data/supervisord/two-services/supervisord.conf: -------------------------------------------------------------------------------- 1 | [include] 2 | files = mocks/*.conf 3 | 4 | [program:mocks-watcher] 5 | environment = A="env 1",B="this is a test" 6 | command = watcher --mocks="hello" 7 | -------------------------------------------------------------------------------- /static/tests/data/supervisord/with-includes/supervisord.conf: -------------------------------------------------------------------------------- 1 | [include] 2 | files = mocks/*.conf 3 | 4 | [program:mocks-watcher] 5 | environment = A="env 1",B="this is a test" 6 | command = watcher --mocks="hello" 7 | -------------------------------------------------------------------------------- /cmd/mockgen/commands/commands.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func CreateCommandRoot() *cobra.Command { 6 | return mockgenCommand( 7 | mockgenFirstRun(), 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /static/tests/data/openapi/petstore/schemas/pet.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | properties: 3 | id: 4 | type: integer 5 | format: int64 6 | name: 7 | type: string 8 | required: 9 | - id 10 | - name 11 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/with-local-imports/api/grpc/import/custom.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package import1; 4 | 5 | option go_package = "gitlab.io/paas/example/import"; 6 | 7 | message CustomEmpty {} 8 | -------------------------------------------------------------------------------- /static/tests/data/openapi/petstore/schemas/error.yml: -------------------------------------------------------------------------------- 1 | type: object 2 | required: 3 | - code 4 | - message 5 | properties: 6 | code: 7 | type: integer 8 | format: int32 9 | message: 10 | type: string 11 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/with-local-imports-as-reciever/api/grpc/local/custom.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package local; 4 | 5 | option go_package = "gitlab.io/paas/example/local"; 6 | 7 | message CustomEmpty {} 8 | -------------------------------------------------------------------------------- /cmd/certgen/Makefile: -------------------------------------------------------------------------------- 1 | CLI_NAME=certgen 2 | 3 | .PHONY: build 4 | build: 5 | @go build -o ${CLI_NAME} *.go 6 | 7 | .PHONY: install 8 | install: 9 | go mod tidy 10 | go build -o out *.go 11 | mv out ${GOBIN}/${CLI_NAME} 12 | -------------------------------------------------------------------------------- /cmd/confgen/Makefile: -------------------------------------------------------------------------------- 1 | CLI_NAME=confgen 2 | 3 | .PHONY: build 4 | build: 5 | @go build -o ${CLI_NAME} *.go 6 | 7 | .PHONY: install 8 | install: 9 | go mod tidy 10 | go build -o out *.go 11 | mv out ${GOBIN}/${CLI_NAME} 12 | -------------------------------------------------------------------------------- /cmd/mockgen/Makefile: -------------------------------------------------------------------------------- 1 | CLI_NAME=mockgen 2 | 3 | .PHONY: build 4 | build: 5 | @go build -o ${CLI_NAME} *.go 6 | 7 | .PHONY: install 8 | install: 9 | go mod tidy 10 | go build -o out *.go 11 | mv out ${GOBIN}/${CLI_NAME} 12 | -------------------------------------------------------------------------------- /cmd/reload/Makefile: -------------------------------------------------------------------------------- 1 | CLI_NAME=reload 2 | 3 | .PHONY: build 4 | build: 5 | @go build -o ${CLI_NAME} *.go 6 | 7 | .PHONY: install 8 | install: 9 | go mod tidy 10 | go build -o out *.go 11 | mv out ${GOBIN}/${CLI_NAME} 12 | -------------------------------------------------------------------------------- /cmd/watcher/Makefile: -------------------------------------------------------------------------------- 1 | CLI_NAME=watcher 2 | 3 | .PHONY: build 4 | build: 5 | @go build -o ${CLI_NAME} *.go 6 | 7 | .PHONY: install 8 | install: 9 | go mod tidy 10 | go build -o out *.go 11 | mv out ${GOBIN}/${CLI_NAME} 12 | -------------------------------------------------------------------------------- /pkg/compiler/const.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | const ( 4 | ProtoCompiler = "protoc" 5 | ProtoGoPlugin = "protoc-gen-go" 6 | ProtoGRPCPlugin = "protoc-gen-go-grpc" 7 | ProtoOpenAPIPlugin = "protoc-gen-openapi" 8 | ) 9 | -------------------------------------------------------------------------------- /scripts/run_wiremock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | bash /docker-entrypoint.sh 2>&1 | logger -t "${WIREMOCK_RUN_HEADER}" & 6 | 7 | bash "${OTHER}/wait_for_it.sh" "${WIREMOCK_ADDR}" | logger -t "${WIREMOCK_RUN_HEADER}" -------------------------------------------------------------------------------- /cmd/grpc2http/Makefile: -------------------------------------------------------------------------------- 1 | CLI_NAME=grpc2http 2 | 3 | .PHONY: build 4 | build: 5 | @go build -o ${CLI_NAME} *.go 6 | 7 | .PHONY: install 8 | install: 9 | go mod tidy 10 | go build -o out *.go 11 | mv out ${GOBIN}/${CLI_NAME} 12 | -------------------------------------------------------------------------------- /scripts/other/log.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | log_header() { 6 | local message="${1}" header="${2}" 7 | 8 | echo "▛ ▞ ▟ ${message}" \ 9 | | tr '[a-z]' '[A-Z]' \ 10 | | logger -t "${header}" 11 | } -------------------------------------------------------------------------------- /static/tests/data/static-examples/with-local-imports-as-reciever-and-golang-keywords/api/grpc/go/custom.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package go; 4 | 5 | option go_package = "gitlab.io/paas/example/go"; 6 | 7 | message CustomEmpty {} 8 | -------------------------------------------------------------------------------- /dev/watch_wiremock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | CompileDaemon -color -log-prefix \ 6 | -pattern "(.+\\.go|.+\\.c|.+\\.sh)$" \ 7 | -build='make install -C cmd/grpc2http' \ 8 | -command='bash ./dev/init_wiremock.sh' 9 | -------------------------------------------------------------------------------- /static/static.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "embed" 5 | 6 | "github.com/spf13/afero" 7 | ) 8 | 9 | //go:embed * 10 | var static embed.FS 11 | 12 | func FromEmbed() afero.Fs { 13 | return afero.FromIOFS{FS: static} 14 | } 15 | -------------------------------------------------------------------------------- /static/tests-statuses.yml: -------------------------------------------------------------------------------- 1 | generation: 2 | skip: 3 | - simple-1-flaky 4 | success: 5 | - simple-success 6 | fail: 7 | - simple-2-fail 8 | 9 | run-some-fancy-test-name: 10 | skip: 11 | - flaky-test-1 -------------------------------------------------------------------------------- /dev/init_wiremock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | killall tail || true 6 | ps | grep '[s]cript' | awk '{print $1}' | xargs -r -n1 kill || true 7 | 8 | 9 | SCRIPTS=$(realpath "$(dirname "${0}")")/../scripts 10 | 11 | $SCRIPTS/init.sh 12 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/without-methods/api/grpc/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "gitlab.io/paas/example"; 6 | 7 | message Request {} 8 | 9 | message Response {} 10 | 11 | service Example {} -------------------------------------------------------------------------------- /pkg/fstesting/entry/entry.go: -------------------------------------------------------------------------------- 1 | package entry 2 | 3 | import "io/fs" 4 | 5 | type Entry struct { 6 | Path string 7 | 8 | fs.FileInfo 9 | } 10 | 11 | func NewEntry(path string, info fs.FileInfo) Entry { 12 | return Entry{FileInfo: info, Path: path} 13 | } 14 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/without-proto-files/api/grpc/example.txt: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "gitlab.io/paas/example"; 6 | 7 | message Request {} 8 | 9 | message Response {} 10 | 11 | service Example {} -------------------------------------------------------------------------------- /static/tests/data/static-examples/with-local-imports-as-reciever-and-golang-keywords/api/grpc/fallthrough/fallthrough.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package fallthrough; 4 | 5 | option go_package = "gitlab.io/paas/example/fallthrough"; 6 | 7 | message CustomFallthroughEmpty {} -------------------------------------------------------------------------------- /static/proxy/template/layout/Makefile: -------------------------------------------------------------------------------- 1 | PROXY_BINARY_NAME=grpc-to-http-proxy 2 | 3 | run: 4 | @go mod tidy 5 | @go run cmd/*.go 6 | 7 | install: 8 | @go mod tidy 9 | @go build -o ${PROXY_BINARY_NAME} cmd/*.go 10 | @mv ${PROXY_BINARY_NAME} ${GOBIN}/${PROXY_BINARY_NAME} 11 | -------------------------------------------------------------------------------- /static/tests/data/supervisord/simple/supervisord.conf: -------------------------------------------------------------------------------- 1 | [program:mock-awesome] 2 | command = java -cp "/var/wiremock/lib/*:/var/wiremock/extensions/*" com.github.tomakehurst.wiremock.standalone.WireMockServerRunner --port 8000 --root-dir static/tests/data/wiremock/configs/two-services/awesome 3 | -------------------------------------------------------------------------------- /.image-build-args: -------------------------------------------------------------------------------- 1 | GOLANG_IMAGE_REPO="registry.hub.docker.com" 2 | GOLANG_IMAGE_NAME="library/golang" 3 | GOLANG_IMAGE_TAG="1.21.7" 4 | 5 | 6 | WIREMOCK_IMAGE_REPO="docker.io" 7 | WIREMOCK_IMAGE_NAME="holomekc/wiremock-gui" 8 | WIREMOCK_IMAGE_TAG="3.6.7-alpine" 9 | 10 | # See COPYRIGHT file. 11 | -------------------------------------------------------------------------------- /scripts/mocks/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | DOMAINS_PATH="${GW_CONTRACTS_PATH}/services" 6 | 7 | if ! mockgen first-run \ 8 | --domains-path="${DOMAINS_PATH}" \ 9 | --wiremock-path="${GW_WIREMOCK_PATH}"; then 10 | 11 | echo "Mocks autogen failed. Skip" 12 | fi 13 | -------------------------------------------------------------------------------- /benchmarks/wiremock/mappings/test_mock_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "request" : { 3 | "urlPath" : "/HealthCheck", 4 | "method" : "GET" 5 | }, 6 | "response" : { 7 | "status" : 200, 8 | "body" : "success", 9 | "headers" : { 10 | "Content-Type" : "application/json" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /dev/example/Dockerfile: -------------------------------------------------------------------------------- 1 | ## Build 2 | FROM golang:1.19-buster AS build 3 | 4 | WORKDIR /sources 5 | 6 | COPY . . 7 | 8 | RUN go mod download 9 | RUN go build -o /app cmd/*.go 10 | 11 | ## Deploy 12 | FROM gcr.io/distroless/base-debian10 13 | 14 | WORKDIR / 15 | 16 | COPY --from=build /app /app 17 | 18 | ENTRYPOINT ["/app"] -------------------------------------------------------------------------------- /benchmarks/scripts/health.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | DEFAULT_PORT=8000 6 | PORT=${DEFAULT_PORT} 7 | 8 | for i in $(seq 1 "${WIREMOCK_API_COUNT}"); do 9 | echo "===> Check health, port: ${PORT}" 10 | curl --fail http://localhost:${PORT}/HealthCheck || exit 1 11 | 12 | PORT=$((PORT+1)) 13 | done 14 | -------------------------------------------------------------------------------- /cmd/reload/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/SberMarket-Tech/grpc-wiremock/cmd/reload/commands" 7 | ) 8 | 9 | func main() { 10 | command := commands.CreateCommandRoot() 11 | 12 | if err := command.Execute(); err != nil { 13 | log.Fatalln("execute cli command:", err.Error()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cmd/certgen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/SberMarket-Tech/grpc-wiremock/cmd/certgen/commands" 7 | ) 8 | 9 | func main() { 10 | command := commands.CreateCommandRoot() 11 | 12 | if err := command.Execute(); err != nil { 13 | log.Fatalln("execute cli command:", err.Error()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cmd/confgen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/SberMarket-Tech/grpc-wiremock/cmd/confgen/commands" 7 | ) 8 | 9 | func main() { 10 | command := commands.CreateCommandRoot() 11 | 12 | if err := command.Execute(); err != nil { 13 | log.Fatalln("execute cli command:", err.Error()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cmd/mockgen/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/SberMarket-Tech/grpc-wiremock/cmd/mockgen/commands" 7 | ) 8 | 9 | func main() { 10 | command := commands.CreateCommandRoot() 11 | 12 | if err := command.Execute(); err != nil { 13 | log.Fatalln("execute cli command:", err.Error()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cmd/watcher/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/SberMarket-Tech/grpc-wiremock/cmd/watcher/commands" 7 | ) 8 | 9 | func main() { 10 | command := commands.CreateCommandRoot() 11 | 12 | if err := command.Execute(); err != nil { 13 | log.Fatalln("execute cli command:", err.Error()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "context" 5 | _ "fmt" 6 | _ "log" 7 | _ "net" 8 | 9 | _ "github.com/grpc-ecosystem/go-grpc-middleware" 10 | _ "go.uber.org/zap" 11 | _ "google.golang.org/grpc" 12 | _ "google.golang.org/protobuf/encoding/protojson" 13 | ) 14 | 15 | func main() { 16 | // Do nothing 17 | } 18 | -------------------------------------------------------------------------------- /etc/rsyslog.d/wiremock.conf: -------------------------------------------------------------------------------- 1 | $RepeatedMsgReduction off 2 | $FileCreateMode 0666 3 | 4 | template(name="WireMockFileFormat" type="string" 5 | string="%syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n") 6 | 7 | $EscapeControlCharactersOnReceive off 8 | 9 | if $syslogtag startswith 'gw.' then /var/log/wiremock;WireMockFileFormat 10 | -------------------------------------------------------------------------------- /static/proxy/template/layout/pkg/getenv/getenv.go: -------------------------------------------------------------------------------- 1 | package getenv 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | ) 7 | 8 | func GetPort() string { 9 | rawPort := os.Getenv("GRPC_TO_HTTP_PROXY_PORT") 10 | 11 | _, err := strconv.ParseInt(rawPort, 10, 0) 12 | if err != nil { 13 | return defaultPort 14 | } 15 | 16 | return rawPort 17 | } 18 | -------------------------------------------------------------------------------- /benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | OUTPUT=output 2 | 3 | .PHONY: clean 4 | clean: 5 | @rm -rf "${OUTPUT}" && mkdir "${OUTPUT}" 6 | 7 | .PHONY: up 8 | up: 9 | @WIREMOCK_API_COUNT=$(WIREMOCK_API_COUNT) && docker compose up --force-recreate --build 10 | 11 | .PHONY: down 12 | down: 13 | docker compose down 14 | 15 | .PHONY: benchmark 16 | benchmark: 17 | bash benchmark.sh 18 | -------------------------------------------------------------------------------- /pkg/utils/executils/look.go: -------------------------------------------------------------------------------- 1 | package executils 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | ) 7 | 8 | func HostHasBinaries(binaries ...string) error { 9 | for _, binary := range binaries { 10 | _, err := exec.LookPath(binary) 11 | if err != nil { 12 | return fmt.Errorf("look path for '%s': %w", binary, err) 13 | } 14 | } 15 | 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /static/tests/data/supervisord/one-service/mocks/mock-awesome.conf: -------------------------------------------------------------------------------- 1 | [program:mock-awesome] 2 | environment = ROOT="/home/mock/awesome",PORT="8000",NAME="awesome" 3 | command = java -cp "/var/wiremock/lib/*:/var/wiremock/extensions/*" com.github.tomakehurst.wiremock.standalone.WireMockServerRunner --port 8000 --root-dir /static/tests/data/wiremock/configs/two-services/awesome 4 | -------------------------------------------------------------------------------- /static/tests/data/supervisord/two-services/mocks/mock-awesome.conf: -------------------------------------------------------------------------------- 1 | [program:mock-awesome] 2 | environment = ROOT="/home/mock/awesome",PORT="8000",NAME="awesome" 3 | command = java -cp "/var/wiremock/lib/*:/var/wiremock/extensions/*" com.github.tomakehurst.wiremock.standalone.WireMockServerRunner --port 8000 --root-dir static/tests/data/wiremock/configs/two-services/awesome 4 | -------------------------------------------------------------------------------- /static/tests/data/supervisord/with-includes/mocks/mock-awesome.conf: -------------------------------------------------------------------------------- 1 | [program:mock-awesome] 2 | environment = ROOT="/home/mock/awesome",PORT="8000",NAME="awesome" 3 | command = java -cp "/var/wiremock/lib/*:/var/wiremock/extensions/*" com.github.tomakehurst.wiremock.standalone.WireMockServerRunner --port 8000 --root-dir static/tests/data/wiremock/configs/two-services/awesome 4 | -------------------------------------------------------------------------------- /pkg/models/mock/mock.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | // Mock is a basic type, which contains a description of a stub. 4 | // Basis for generating a Wiremock stub. 5 | type Mock struct { 6 | Name string 7 | Description string 8 | RequestUrlPath string 9 | RequestMethod string 10 | ResponseBody string 11 | ResponseStatusCode int 12 | } 13 | -------------------------------------------------------------------------------- /static/tests/data/supervisord/two-services/mocks/mock-push-sender.conf: -------------------------------------------------------------------------------- 1 | [program:mock-push-sender] 2 | environment = ROOT="/home/mock/push-sender",PORT="8001",NAME="push-sender" 3 | command = java -cp "/var/wiremock/lib/*:/var/wiremock/extensions/*" com.github.tomakehurst.wiremock.standalone.WireMockServerRunner --port 8001 --root-dir static/tests/data/wiremock/configs/two-services/awesome 4 | -------------------------------------------------------------------------------- /scripts/proxy/watch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Watch contracts and restart grpc-to-http proxy. 6 | CompileDaemon \ 7 | -run-dir="/var/proxy" \ 8 | -directory="${GW_CONTRACTS_PATH}" \ 9 | -build="${PROXY}/run.sh" \ 10 | -command="grpc-to-http-proxy" \ 11 | -pattern="(.+\\.)proto$" \ 12 | -log-prefix=false \ 13 | -graceful-kill -graceful-timeout=3 14 | -------------------------------------------------------------------------------- /pkg/generators/proxy/const.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const pkgDir = "grpc-proxy/pkg" 9 | 10 | // toOriginalPackageName helps to convert modified for 11 | // generation purposes `go_package` to original one. 12 | func toOriginalPackageName(goPackage string) string { 13 | return strings.TrimPrefix(goPackage, fmt.Sprintf("%s/", pkgDir)) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/utils/sliceutils/sliceutils.go: -------------------------------------------------------------------------------- 1 | package sliceutils 2 | 3 | func FirstOf[T any](slice []T) T { 4 | var result T 5 | 6 | if len(slice) > 0 { 7 | result = slice[0] 8 | } 9 | 10 | return result 11 | } 12 | 13 | func SliceToMap[T comparable](slice []T) map[T]struct{} { 14 | mapAsSet := map[T]struct{}{} 15 | 16 | for _, el := range slice { 17 | mapAsSet[el] = struct{}{} 18 | } 19 | 20 | return mapAsSet 21 | } 22 | -------------------------------------------------------------------------------- /dev/example/deps/services/push-sender/grpc/push-sender.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package push_sender; 4 | 5 | option go_package = "github.com/nktch1/pkg/clients/push_sender"; 6 | 7 | service PushSender { 8 | rpc Notify(NotifyRequest) returns (NotifyResponse); 9 | } 10 | 11 | message NotifyRequest { 12 | string uuid = 1; 13 | string message = 2; 14 | } 15 | 16 | message NotifyResponse { 17 | uint32 status = 1; 18 | } -------------------------------------------------------------------------------- /benchmarks/Dockerfile-wiremock: -------------------------------------------------------------------------------- 1 | ARG WIREMOCK_IMAGE_TAG="2.32.0" 2 | ARG WIREMOCK_IMAGE_REPO="wiremock" 3 | ARG WIREMOCK_IMAGE_NAME="wiremock" 4 | 5 | ARG WIREMOCK_API_COUNT 6 | 7 | FROM ${WIREMOCK_IMAGE_REPO}/${WIREMOCK_IMAGE_NAME}:${WIREMOCK_IMAGE_TAG} 8 | 9 | # Scripts 10 | COPY scripts /scripts 11 | RUN chmod -R +x /scripts 12 | 13 | ENV WIREMOCK_API_COUNT=${WIREMOCK_API_COUNT} 14 | 15 | ENTRYPOINT ["/scripts/entrypoint.sh"] 16 | -------------------------------------------------------------------------------- /pkg/renderer/funcs.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import ( 4 | "path" 5 | "strings" 6 | "text/template" 7 | 8 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/strutils" 9 | ) 10 | 11 | var funcMap = template.FuncMap{ 12 | "Base": path.Base, 13 | "Capitalize": strings.Title, 14 | "ToLower": strings.ToLower, 15 | "ToSnakeCase": strutils.ToSnakeCase, 16 | "ToPackageName": strutils.ToPackageName, 17 | } 18 | -------------------------------------------------------------------------------- /static/proxy/files/service.go.tpl: -------------------------------------------------------------------------------- 1 | package {{ .PackageHeader }} 2 | 3 | import ( 4 | "google.golang.org/grpc" 5 | 6 | "{{ .GoPackage }}" 7 | ) 8 | 9 | type Service struct { 10 | {{ .Package }}.Unimplemented{{ .Service }}Server 11 | } 12 | 13 | func NewService() *Service { 14 | return &Service{} 15 | } 16 | 17 | func (p *Service) RegisterGRPC(server *grpc.Server) { 18 | {{ .Package }}.Register{{ .Service }}Server(server, p) 19 | } 20 | -------------------------------------------------------------------------------- /static/supervisord/files/supervisord.conf.tpl: -------------------------------------------------------------------------------- 1 | [program:mock-{{ .Domain }}-{{ .Port }}] 2 | environment = ROOT="{{ .Root }}",PORT="{{ .Port }}",NAME="{{ .Domain }}" 3 | autorestart = true 4 | redirect_stderr = true 5 | stdout_logfile = /var/log/supervisord/mock-{{ .Domain }}.log 6 | command = java -cp "/var/wiremock/lib/*:/var/wiremock/extensions/*" wiremock.Run --port {{ .Port }} --root-dir {{ .Root }} --global-response-templating --record-mappings --verbose 7 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/without-gopackage/api/grpc/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | message Request {} 6 | 7 | message Response {} 8 | 9 | service Example { 10 | rpc Unary(Request) returns (Response); 11 | rpc ClientSideStream(stream Request) returns (Response); 12 | rpc ServerSideStream(Request) returns (stream Response); 13 | rpc BidirectionalStream(stream Request) returns (stream Response); 14 | } -------------------------------------------------------------------------------- /dev/example/api/grpc/wearable.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package wearable; 4 | 5 | option go_package = "github.com/nktch1/pkg/server/wearable"; 6 | 7 | service WearableService { 8 | rpc BeatsPerMinute(BeatsPerMinuteRequest) returns (stream BeatsPerMinuteResponse) {}; 9 | } 10 | 11 | message BeatsPerMinuteRequest { 12 | string uuid = 1; 13 | } 14 | 15 | message BeatsPerMinuteResponse { 16 | uint32 value = 1; 17 | uint32 minute = 2; 18 | } -------------------------------------------------------------------------------- /static/supervisord/default-config-path/supervisord.conf: -------------------------------------------------------------------------------- 1 | [include] 2 | files = /etc/supervisord/mocks/*.conf 3 | 4 | [inet_http_server] 5 | port=127.0.0.1:9000 6 | ;username=test1 7 | ;password=thepassword 8 | 9 | [supervisorctl] 10 | serverurl=http://127.0.0.1:9000 11 | 12 | [program:watcher_domains] 13 | command = watcher --domains=/home/mock 14 | autorestart = true 15 | redirect_stderr = true 16 | stdout_logfile = watcher_domains.log, /dev/stdout 17 | -------------------------------------------------------------------------------- /pkg/jsonschema/oapimock.go: -------------------------------------------------------------------------------- 1 | package jsonschema 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/jsonschema/datagen" 7 | ) 8 | 9 | func GenerateJSONDataByJSONSchema(schema []byte) ([]byte, error) { 10 | generator := datagen.New() 11 | 12 | data, err := generator.Generate(schema) 13 | if err != nil { 14 | return nil, fmt.Errorf("generate data based on json schema: %w", err) 15 | } 16 | 17 | return data, nil 18 | } 19 | -------------------------------------------------------------------------------- /pkg/printer/print.go: -------------------------------------------------------------------------------- 1 | package printer 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jhump/protoreflect/desc" 7 | "github.com/jhump/protoreflect/desc/protoprint" 8 | ) 9 | 10 | func Print(descriptors []*desc.FileDescriptor, outputPath string) error { 11 | printer := protoprint.Printer{} 12 | 13 | if err := printer.PrintProtosToFileSystem(descriptors, outputPath); err != nil { 14 | return fmt.Errorf("print: %w", err) 15 | } 16 | 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /benchmarks/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | WIREMOCK_API_COUNT="${1}" 6 | DEFAULT_PORT=8000 7 | PORT=${DEFAULT_PORT} 8 | 9 | for i in $(seq 1 "${WIREMOCK_API_COUNT}"); do 10 | echo "===> Run Wiremock API, port: ${PORT}" 11 | 12 | java \ 13 | -cp "/var/wiremock/lib/*:/var/wiremock/extensions/*" \ 14 | com.github.tomakehurst.wiremock.standalone.WireMockServerRunner --port ${PORT} & 15 | 16 | PORT=$((PORT+1)) 17 | done 18 | 19 | sleep infinity -------------------------------------------------------------------------------- /static/tests/data/static-examples/simple/api/grpc/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "gitlab.io/paas/example"; 6 | 7 | message Request {} 8 | 9 | message Response {} 10 | 11 | service Example { 12 | rpc Unary(Request) returns (Response); 13 | rpc ClientSideStream(stream Request) returns (Response); 14 | rpc ServerSideStream(Request) returns (stream Response); 15 | rpc BidirectionalStream(stream Request) returns (stream Response); 16 | } -------------------------------------------------------------------------------- /static/tests/data/static-examples/without-gopackage-with-local-imports/api/grpc/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | import "another.proto"; 6 | import "import/another.proto"; 7 | 8 | message Request {} 9 | 10 | service Example { 11 | rpc Unary(Request) returns (Response); 12 | rpc ClientSideStream(stream Request) returns (Response); 13 | rpc ServerSideStream(Request) returns (stream Response); 14 | rpc BidirectionalStream(stream Request) returns (stream inner.AnotherResponse); 15 | } -------------------------------------------------------------------------------- /benchmarks/scripts/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | DEFAULT_PORT=8000 6 | PORT=${DEFAULT_PORT} 7 | OUT_DIR="/benchmarks/output/api_count_${WIREMOCK_API_COUNT}_$(date --iso=seconds)" 8 | 9 | mkdir "${OUT_DIR}" 10 | 11 | for i in $(seq 1 "${WIREMOCK_API_COUNT}"); do 12 | echo "===> Benchmark Wiremock API, port: ${PORT}" 13 | 14 | ab -c 10 -n 100 "http://wiremock:${PORT}/HealthCheck" > \ 15 | "${OUT_DIR}/report_${PORT}.txt" 2>&1 & 16 | 17 | PORT=$((PORT+1)) 18 | done 19 | 20 | sleep infinity -------------------------------------------------------------------------------- /pkg/models/basecontract/from_openapi.go: -------------------------------------------------------------------------------- 1 | package basecontract 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/getkin/kin-openapi/openapi3" 7 | ) 8 | 9 | func FromOpenAPIDescriptor(descriptor *openapi3.T, path string) (Contract, error) { 10 | if descriptor == nil { 11 | return Contract{}, fmt.Errorf("descriptor is nil") 12 | } 13 | 14 | contract := Contract{ 15 | HeaderPath: path, 16 | 17 | ContractHasMethods: len(descriptor.Paths) != 0, 18 | 19 | descriptor: descriptor, 20 | } 21 | 22 | return contract, nil 23 | } 24 | -------------------------------------------------------------------------------- /scripts/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | log_header "Init Multi-API" "${ENTRYPOINT_HEADER}" 6 | bash "${MULTIAPI}/init.sh" 7 | 8 | log_header "Install grpc-to-http proxy" "${ENTRYPOINT_HEADER}" 9 | bash "${PROXY}/init.sh" 10 | 11 | log_header "Setup mocks" "${ENTRYPOINT_HEADER}" 12 | bash "${MOCKS}/init.sh" 13 | 14 | log_header "Install certificates. Setup nginx" "${ENTRYPOINT_HEADER}" 15 | bash "${ROUTING}/init.sh" 16 | 17 | log_header "Initialization is done" "${ENTRYPOINT_HEADER}" 18 | bash "${ROUTING}/logs.sh" 19 | -------------------------------------------------------------------------------- /static/proxy-nginx/files/nginx.conf.tpl: -------------------------------------------------------------------------------- 1 | server { 2 | server_name {{ .Domain }} {{ .Domain }}.*; 3 | 4 | listen 80; 5 | listen [::]:80; 6 | listen 443 ssl; 7 | 8 | ssl_certificate /etc/ssl/mock/mock.crt; 9 | ssl_certificate_key /etc/ssl/mock/mock.key; 10 | 11 | access_log /var/log/nginx/{{ .Domain }}.access.log main; 12 | 13 | location / { 14 | proxy_set_header Host $host; 15 | proxy_set_header X-Real-IP $remote_addr; 16 | proxy_pass http://localhost:{{ .Port }}; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/routing/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if ! certgen | logger -t "${ROUTING_CERTS_GEN_HEADER}"; then 6 | echo "Certificate initial generating failed. Skip" 7 | fi 8 | 9 | [ "$(ps | grep '[n]ginx')" == "" ] && sudo nginx 10 | 11 | if ! confgen | logger -t "${ROUTING_NGINX_GEN_HEADER}"; then 12 | echo "Nginx configs generator failed. Skip" 13 | fi 14 | 15 | if ! certgen | logger -t "${ROUTING_CERTS_GEN_HEADER}"; then 16 | echo "Certificate generating for mock hosts failed. Skip" 17 | fi 18 | 19 | sudo nginx -s reload 20 | -------------------------------------------------------------------------------- /benchmarks/Dockerfile-benchmarks: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE_TAG="1.19" 2 | ARG BASE_IMAGE_REPO="registry.hub.docker.com/library" 3 | ARG BASE_IMAGE_NAME="golang" 4 | 5 | ARG WIREMOCK_API_COUNT 6 | 7 | FROM ${BASE_IMAGE_REPO}/${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG} 8 | 9 | ENV WIREMOCK_API_COUNT=${WIREMOCK_API_COUNT} 10 | 11 | RUN apt-get update && apt-get install apache2-utils 12 | 13 | COPY scripts /benchmarks 14 | COPY Makefile /benchmarks/Makefile 15 | 16 | WORKDIR /benchmarks 17 | 18 | #RUN go mod init test && go mod tidy 19 | 20 | ENTRYPOINT ["make", "benchmark"] 21 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/with-local-imports-as-reciever/api/grpc/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "gitlab.io/paas/example"; 6 | 7 | import "local/custom.proto"; 8 | 9 | message Request {} 10 | 11 | message Response {} 12 | 13 | service Example { 14 | rpc Unary(local.CustomEmpty) returns (Response); 15 | rpc ClientSideStream(stream Request) returns (Response); 16 | rpc ServerSideStream(Request) returns (stream Response); 17 | rpc BidirectionalStream(stream Request) returns (stream Response); 18 | } -------------------------------------------------------------------------------- /pkg/wiremock/config/ports_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestPorts_Allocate(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | p Ports 13 | want int 14 | }{ 15 | {p: Ports{8000, 8002, 8003}, want: 8004}, 16 | {p: Ports{8007}, want: 8008}, 17 | {p: Ports{}, want: 8000}, 18 | } 19 | 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | got := tt.p.Allocate() 23 | require.Equal(t, tt.want, got) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /static/health/health.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package grpc.health.v1; 4 | 5 | option go_package = "grpc-proxy/pkg/gitlab.sbmt.io/paas/health"; 6 | 7 | message HealthCheckRequest { 8 | string service = 1; 9 | } 10 | 11 | message HealthCheckResponse { 12 | enum ServingStatus { 13 | UNKNOWN = 0; 14 | SERVING = 1; 15 | NOT_SERVING = 2; 16 | } 17 | ServingStatus status = 1; 18 | } 19 | 20 | service Health { 21 | rpc Check(HealthCheckRequest) returns (HealthCheckResponse); 22 | rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse); 23 | } -------------------------------------------------------------------------------- /static/tests/data/static-examples/with-local-imports/api/grpc/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "gitlab.io/paas/example"; 6 | 7 | import "import/custom.proto"; 8 | 9 | message Request { 10 | import1.CustomEmpty name = 1; 11 | } 12 | 13 | message Response {} 14 | 15 | service Example { 16 | rpc Unary(Request) returns (Response); 17 | rpc ClientSideStream(stream Request) returns (Response); 18 | rpc ServerSideStream(Request) returns (stream Response); 19 | rpc BidirectionalStream(stream Request) returns (stream Response); 20 | } -------------------------------------------------------------------------------- /docs/comparsion.md: -------------------------------------------------------------------------------- 1 | ## Comparison 2 | 3 | Here is a comparison of existing solutions for deploying mock servers, 4 | as well as a comparison of existing Wiremock extensions with our 5 | implementation. 6 | 7 | ### Mock servers 8 | 9 | TODO Критерии сравнения сервисов 10 | 11 | ### Existing extensions for Wiremock 12 | 13 | The most notable implementation of gRPC over Wiremock is this [one](https://github.com/Adven27/grpc-wiremock). 14 | 15 | | features | grpc-wiremock | grpc-wiremock (java) | 16 | |---------|---------|----------------------| 17 | | | | | 18 | 19 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/with-common-imports/api/grpc/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "gitlab.io/paas/example"; 6 | 7 | import "google/protobuf/empty.proto"; 8 | 9 | message Request { 10 | google.protobuf.Empty name = 1; 11 | } 12 | 13 | message Response {} 14 | 15 | service Example { 16 | rpc Unary(Request) returns (Response); 17 | rpc ClientSideStream(stream Request) returns (Response); 18 | rpc ServerSideStream(Request) returns (stream Response); 19 | rpc BidirectionalStream(stream Request) returns (stream Response); 20 | } -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 Thomas Akehurst 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /pkg/utils/executils/fakecmd.go: -------------------------------------------------------------------------------- 1 | package executils 2 | 3 | import ( 4 | "k8s.io/utils/exec" 5 | testingexec "k8s.io/utils/exec/testing" 6 | ) 7 | 8 | func MakeFakeCmd(fakeCmd *testingexec.FakeCmd, cmd string, args ...string) testingexec.FakeCommandAction { 9 | c := cmd 10 | a := args 11 | return func(cmd string, args ...string) exec.Cmd { 12 | return testingexec.InitFakeCmd(fakeCmd, c, a...) 13 | } 14 | } 15 | 16 | func MakeFakeOutput(output string, err error) testingexec.FakeAction { 17 | o := output 18 | return func() ([]byte, []byte, error) { 19 | return []byte(o), nil, err 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/generators/configs/nginx/reload.go: -------------------------------------------------------------------------------- 1 | package nginx 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/sliceutils" 8 | ) 9 | 10 | type commandRunner interface { 11 | Run(ctx context.Context, cmd string, args ...string) error 12 | } 13 | 14 | type Reloader struct { 15 | Command []string 16 | Runner commandRunner 17 | } 18 | 19 | func (r Reloader) ReloadConfig(ctx context.Context) error { 20 | if err := r.Runner.Run(ctx, sliceutils.FirstOf(r.Command), r.Command[1:]...); err != nil { 21 | return fmt.Errorf("reload: %w", err) 22 | } 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /pkg/generators/configs/supervisord/reload.go: -------------------------------------------------------------------------------- 1 | package supervisord 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/sliceutils" 8 | ) 9 | 10 | type commandRunner interface { 11 | Run(ctx context.Context, cmd string, args ...string) error 12 | } 13 | 14 | type Reloader struct { 15 | Command []string 16 | Runner commandRunner 17 | } 18 | 19 | func (r Reloader) ReloadConfig(ctx context.Context) error { 20 | if err := r.Runner.Run(ctx, sliceutils.FirstOf(r.Command), r.Command[1:]...); err != nil { 21 | return fmt.Errorf("reload: %w", err) 22 | } 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /dev/example/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nktch1/wearable 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 7 | go.uber.org/zap v1.24.0 8 | google.golang.org/grpc v1.62.0 9 | google.golang.org/protobuf v1.32.0 10 | ) 11 | 12 | require ( 13 | github.com/golang/protobuf v1.5.3 // indirect 14 | go.uber.org/atomic v1.7.0 // indirect 15 | go.uber.org/multierr v1.6.0 // indirect 16 | golang.org/x/net v0.22.0 // indirect 17 | golang.org/x/sys v0.18.0 // indirect 18 | golang.org/x/text v0.14.0 // indirect 19 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash -euo pipefail 2 | 3 | .PHONY: check_environment 4 | check_environment: 5 | @$(shell pwd)/scripts/helpers/check_dependencies.sh 6 | 7 | .PHONY: coverage 8 | coverage: 9 | @go-acc $(shell go list ./... | grep -v -e static) && go tool cover -func=coverage.txt 10 | 11 | .PHONY: unit-tests 12 | unit-tests: 13 | @go test $(shell go list ./... | grep -v -e static) -count 1 -race 14 | 15 | .PHONY: install-cli 16 | install-cli: 17 | make install -C cmd/grpc2http 18 | make install -C cmd/mockgen 19 | make install -C cmd/confgen 20 | make install -C cmd/watcher 21 | make install -C cmd/certgen 22 | make install -C cmd/reload 23 | -------------------------------------------------------------------------------- /pkg/wiremock/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | EmptyWiremockConfigErr = fmt.Errorf("empty wiremock config") 10 | 11 | NoCorrespondingAPIErr = fmt.Errorf("no API corresponding to the provided domain") 12 | ) 13 | 14 | type Service struct { 15 | Name string `json:"name"` 16 | Port int `json:"port"` 17 | RootDir string `json:"rootDir"` 18 | } 19 | 20 | type Wiremock struct { 21 | Services []Service `json:"services"` 22 | } 23 | 24 | func NewService(root, domain string, port int) Service { 25 | return Service{Name: domain, Port: port, RootDir: filepath.Join(root, domain)} 26 | } 27 | -------------------------------------------------------------------------------- /pkg/errgroup/errgroup.go: -------------------------------------------------------------------------------- 1 | package errgroup 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | type Group struct { 11 | *errgroup.Group 12 | } 13 | 14 | func WithContext(parent context.Context) (*Group, context.Context) { 15 | g, ctx := errgroup.WithContext(parent) 16 | return &Group{g}, ctx 17 | } 18 | 19 | func (g *Group) Go(originFn func() error) { 20 | fn := func() (err error) { 21 | defer func() { 22 | if r := recover(); r != nil { 23 | err = fmt.Errorf("recovered panic from gorutine: %v", r) 24 | } 25 | }() 26 | 27 | err = originFn() 28 | 29 | return 30 | } 31 | 32 | g.Group.Go(fn) 33 | } 34 | -------------------------------------------------------------------------------- /static/proxy/template/layout/go.mod.rename.me: -------------------------------------------------------------------------------- 1 | module grpc-proxy 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/stretchr/testify v1.8.1 7 | google.golang.org/grpc v1.62.0 8 | google.golang.org/protobuf v1.32.0 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/golang/protobuf v1.5.3 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | golang.org/x/net v0.22.0 // indirect 16 | golang.org/x/sys v0.18.0 // indirect 17 | golang.org/x/text v0.14.0 // indirect 18 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /example/go.mod: -------------------------------------------------------------------------------- 1 | module grpc-proxy 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 7 | go.uber.org/zap v1.23.0 8 | google.golang.org/grpc v1.62.0 9 | google.golang.org/protobuf v1.32.0 10 | ) 11 | 12 | require ( 13 | github.com/golang/protobuf v1.5.3 // indirect 14 | github.com/pkg/errors v0.9.1 // indirect 15 | go.uber.org/atomic v1.10.0 // indirect 16 | go.uber.org/multierr v1.8.0 // indirect 17 | golang.org/x/net v0.22.0 // indirect 18 | golang.org/x/sys v0.18.0 // indirect 19 | golang.org/x/text v0.14.0 // indirect 20 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240304212257-790db918fca8 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /scripts/proxy/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | PROXY_HOST="http://localhost" 6 | PROXY_INPUT_PORT="3010" 7 | PROXY_OUTPUT_PORT="80" 8 | PROXY_BASE_URL="${PROXY_HOST}:${PROXY_OUTPUT_PORT}" 9 | PROXY_OUTPUT_PATH="/var/proxy/proxy" 10 | 11 | # Generate new proxy. 12 | rm -rf ${PROXY_OUTPUT_PATH} 13 | mkdir -p ${PROXY_OUTPUT_PATH} 14 | 15 | grpc2http \ 16 | --input "${GW_CONTRACTS_PATH}" \ 17 | --output "${PROXY_OUTPUT_PATH}" \ 18 | --base-url "${PROXY_BASE_URL}" 19 | 20 | # Install new proxy. 21 | make -C "${PROXY_OUTPUT_PATH}" install 22 | 23 | # Kill previous proxy instance if exists. 24 | lsof -t -i:${PROXY_INPUT_PORT} | xargs -r kill || true 25 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/with-local-imports-as-reciever-and-golang-keywords/api/grpc/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "gitlab.io/paas/example"; 6 | 7 | import "go/custom.proto"; 8 | import "fallthrough/fallthrough.proto"; 9 | 10 | service Example { 11 | rpc Unary(fallthrough.CustomFallthroughEmpty) returns (go.CustomEmpty); 12 | rpc ClientSideStream(stream fallthrough.CustomFallthroughEmpty) returns (go.CustomEmpty); 13 | rpc ServerSideStream(fallthrough.CustomFallthroughEmpty) returns (stream go.CustomEmpty); 14 | rpc BidirectionalStream(stream fallthrough.CustomFallthroughEmpty) returns (stream go.CustomEmpty); 15 | } -------------------------------------------------------------------------------- /pkg/models/protocontract/traverser/traverse.go: -------------------------------------------------------------------------------- 1 | package traverser 2 | 3 | import ( 4 | "github.com/jhump/protoreflect/desc" 5 | 6 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/protocontract" 7 | ) 8 | 9 | func Descriptors(contracts protocontract.SetOfContracts) []*desc.FileDescriptor { 10 | var all []*desc.FileDescriptor 11 | 12 | for _, cont := range contracts { 13 | all = append(all, cont.Desc) 14 | rec(cont.Desc, &all) 15 | } 16 | 17 | return all 18 | } 19 | 20 | func rec(desc *desc.FileDescriptor, all *[]*desc.FileDescriptor) { 21 | *all = append(*all, desc.GetDependencies()...) 22 | for _, descriptor := range desc.GetDependencies() { 23 | rec(descriptor, all) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/jsonschema/datagen/datagen.go: -------------------------------------------------------------------------------- 1 | package datagen 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | gen "github.com/apicat/datagen" 8 | ) 9 | 10 | type datagen struct{} 11 | 12 | func New() datagen { return datagen{} } 13 | 14 | // Generate allows to get randomized JSON samples based on provided JSON schema. 15 | func (d datagen) Generate(schema []byte) ([]byte, error) { 16 | opt := gen.GenOption{} 17 | 18 | data, err := gen.JSONSchemaGen(schema, &opt) 19 | if err != nil { 20 | return nil, fmt.Errorf("call json schema gen: %w", err) 21 | } 22 | 23 | dataBytes, err := json.Marshal(data) 24 | if err != nil { 25 | return nil, fmt.Errorf("marshal json output: %w", err) 26 | } 27 | 28 | return dataBytes, nil 29 | } 30 | -------------------------------------------------------------------------------- /pkg/runner/errors.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type errUnknownCommandExecution struct { 9 | Err error 10 | 11 | Command string 12 | Output string 13 | Args []string 14 | } 15 | 16 | func (e errUnknownCommandExecution) Error() string { 17 | var builder strings.Builder 18 | 19 | builder.WriteString("command execution unknown error:\n") 20 | builder.WriteString(fmt.Sprintf("\t command: '%s'\n", e.Command)) 21 | builder.WriteString(fmt.Sprintf("\t args: '%s'\n", strings.Join(e.Args, " "))) 22 | builder.WriteString(fmt.Sprintf("\t output: '%s'\n", e.Output)) 23 | 24 | return builder.String() 25 | } 26 | 27 | func (e errUnknownCommandExecution) Unwrap() error { 28 | return e.Err 29 | } 30 | -------------------------------------------------------------------------------- /pkg/blacklist/blacklist_test.go: -------------------------------------------------------------------------------- 1 | package blacklist 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIsDeliveredWithProtoc(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | 13 | value string 14 | 15 | want bool 16 | }{ 17 | {value: "google.golang.org/protobuf/reflect/protoreflect", want: true}, 18 | {value: "google.golang.org/protobuf/runtime/protoimpl", want: true}, 19 | {value: "google/protobuf/wrapper.proto", want: true}, 20 | {value: "google.golang.org/grpc/codes", want: false}, 21 | } 22 | 23 | for _, tt := range tests { 24 | t.Run(tt.name, func(t *testing.T) { 25 | got := IsDeliveredWithProtoc(tt.value) 26 | assert.Equal(t, tt.want, got) 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /benchmarks/scripts/report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | PATH_TO_REPORTS="${1-output}" 6 | 7 | reports='[]' 8 | 9 | for path in $(find "${PATH_TO_REPORTS}" -type d -name "api_*"); do 10 | api_count="$(echo "${path}" | grep -Eo '[0-9]+$')" 11 | 12 | report=$(echo $(find "${path}" -name '*.txt' -exec tail -1 {} \; | awk '{print $2}' | xargs) \ 13 | | jq --arg api_count "${api_count}" -s "{ min_response_time_api_count_$api_count:min, max_response_time_api_count_$api_count:max, avg_response_time_api_count_$api_count: (add/length), median_response_time_api_count_$api_count: (sort|.[(length/2|floor)]) }") 14 | 15 | reports=$(echo "${reports}" | jq ". + [${report}]") 16 | done 17 | 18 | echo "${reports}" | jq > "${PATH_TO_REPORTS}/report.json" 19 | -------------------------------------------------------------------------------- /static/tests/data/static-examples/two-service/api/grpc/example.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package example; 4 | 5 | option go_package = "gitlab.io/paas/example"; 6 | 7 | message Request {} 8 | 9 | message Response {} 10 | 11 | service Example { 12 | rpc Unary(Request) returns (Response); 13 | rpc ClientSideStream(stream Request) returns (Response); 14 | rpc ServerSideStream(Request) returns (stream Response); 15 | rpc BidirectionalStream(stream Request) returns (stream Response); 16 | } 17 | 18 | service Example2 { 19 | rpc Unary(Request) returns (Response); 20 | rpc ClientSideStream(stream Request) returns (Response); 21 | rpc ServerSideStream(Request) returns (stream Response); 22 | rpc BidirectionalStream(stream Request) returns (stream Response); 23 | } -------------------------------------------------------------------------------- /dev/watch_build_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | 6 | BUILD_IMAGE=./dev/build_image.sh 7 | REF=master 8 | 9 | TAG="sbermarkettech/grpc-wiremock:dev" 10 | BUILD_ARGS=$(grep -v -E "^#.*|^$" .image-build-args | sed 's@^@--build-arg @g' | xargs) 11 | 12 | COMMAND="docker build . $BUILD_ARGS --tag=$TAG" 13 | 14 | 15 | cat < ${BUILD_IMAGE} 16 | #!/bin/bash 17 | 18 | set -euo pipefail 19 | 20 | echo ${COMMAND} 21 | 22 | ${COMMAND} 23 | EOF 24 | 25 | perl -i -pe 's/ --/ \\\n --/g' ${BUILD_IMAGE} 26 | 27 | chmod +x ${BUILD_IMAGE} 28 | 29 | 30 | CompileDaemon \ 31 | -command="${BUILD_IMAGE}" \ 32 | -directory=. -build='true' \ 33 | -color -log-prefix=false \ 34 | -pattern='(Dockerfile|.image-build-args|.*\.sh|.*\.conf|.*\.tpl|.*\.list)$' 35 | -------------------------------------------------------------------------------- /etc/supervisord/supervisord.conf: -------------------------------------------------------------------------------- 1 | [include] 2 | files = /etc/supervisord/mocks/*.conf 3 | 4 | [supervisord] 5 | loglevel = error 6 | logfile = /var/log/supervisord/supervisord.log 7 | pidfile = /var/log/supervisord/supervisord_pid 8 | 9 | [inet_http_server] 10 | port=0.0.0.0:9000 11 | 12 | [supervisorctl] 13 | serverurl=http://0.0.0.0:9000 14 | 15 | # Turn off domains' watcher. 16 | 17 | # [program:watcher-domains] 18 | # command = watcher --domains=/home/mock 19 | # autorestart = true 20 | # redirect_stderr = true 21 | # stdout_logfile = /var/log/supervisord/watcher-domains.log, /dev/stdout 22 | 23 | [program:watcher-mocks] 24 | command = watcher --mocks=/home/mock 25 | autorestart = true 26 | redirect_stderr = true 27 | stdout_logfile = /var/log/supervisord/watcher-mocks.log, /dev/stdout 28 | -------------------------------------------------------------------------------- /pkg/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strings" 8 | 9 | "k8s.io/utils/exec" 10 | ) 11 | 12 | type shell struct { 13 | exec exec.Interface 14 | } 15 | 16 | func New(ex exec.Interface) shell { 17 | return shell{exec: ex} 18 | } 19 | 20 | func (s shell) Run(ctx context.Context, cmd string, args ...string) error { 21 | rawOut, err := s.exec.CommandContext(ctx, cmd, args...).CombinedOutput() 22 | if err == nil { 23 | return nil 24 | } 25 | 26 | out := strings.TrimSpace(string(rawOut)) 27 | switch { 28 | case errors.Is(err, exec.ErrExecutableNotFound): 29 | return fmt.Errorf("'%s' not found on host: %w", cmd, err) 30 | default: 31 | return errUnknownCommandExecution{Command: cmd, Output: out, Args: args, Err: err} 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/watcher/notifier.go: -------------------------------------------------------------------------------- 1 | package watcher 2 | 3 | import ( 4 | "github.com/farmergreg/rfsnotify" 5 | "gopkg.in/fsnotify.v1" 6 | ) 7 | 8 | type Notifier interface { 9 | Errors() chan error 10 | Events() chan fsnotify.Event 11 | } 12 | 13 | type RecursiveNotifier struct { 14 | notifier *rfsnotify.RWatcher 15 | } 16 | 17 | func (e RecursiveNotifier) Errors() chan error { 18 | return e.notifier.Errors 19 | } 20 | 21 | func (e RecursiveNotifier) Events() chan fsnotify.Event { 22 | return e.notifier.Events 23 | } 24 | 25 | type NonRecursiveNotifier struct { 26 | notifier *fsnotify.Watcher 27 | } 28 | 29 | func (e NonRecursiveNotifier) Errors() chan error { 30 | return e.notifier.Errors 31 | } 32 | 33 | func (e NonRecursiveNotifier) Events() chan fsnotify.Event { 34 | return e.notifier.Events 35 | } 36 | -------------------------------------------------------------------------------- /static/tests/data/openapi/petstore/petstore.yaml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.0" 2 | info: 3 | version: 1.0.0 4 | title: Petstore 5 | paths: 6 | /pets: 7 | get: 8 | operationId: listPets 9 | responses: 10 | '200': 11 | description: pet response 12 | content: 13 | application/json: 14 | schema: 15 | type: array 16 | items: 17 | $ref: '#/components/schemas/Pet' 18 | default: 19 | description: unexpected error 20 | content: 21 | application/json: 22 | schema: 23 | $ref: '#/components/schemas/Error' 24 | components: 25 | schemas: 26 | Pet: 27 | $ref: './schemas/pet.yml' 28 | Error: 29 | $ref: './schemas/error.yml' 30 | -------------------------------------------------------------------------------- /cmd/reload/commands/reload.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | certgencmd "github.com/SberMarket-Tech/grpc-wiremock/cmd/certgen/commands" 9 | confgencmd "github.com/SberMarket-Tech/grpc-wiremock/cmd/confgen/commands" 10 | ) 11 | 12 | func reloadCommand() *cobra.Command { 13 | command := &cobra.Command{ 14 | Use: "reload", 15 | RunE: func(cmd *cobra.Command, _ []string) error { 16 | ctx := cmd.Context() 17 | 18 | if err := certgencmd.CreateCommandRoot().ExecuteContext(ctx); err != nil { 19 | return fmt.Errorf("run certgen: %w", err) 20 | } 21 | 22 | if err := confgencmd.CreateCommandRoot().ExecuteContext(ctx); err != nil { 23 | return fmt.Errorf("run confgen: %w", err) 24 | } 25 | 26 | return nil 27 | }, 28 | } 29 | 30 | return command 31 | } 32 | -------------------------------------------------------------------------------- /dev/build_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | echo docker build . \ 6 | --build-arg GOLANG_IMAGE_REPO=registry.hub.docker.com \ 7 | --build-arg GOLANG_IMAGE_NAME=library/golang \ 8 | --build-arg GOLANG_IMAGE_TAG=1.21.7 \ 9 | --build-arg WIREMOCK_IMAGE_REPO=docker.io \ 10 | --build-arg WIREMOCK_IMAGE_NAME=wiremock/wiremock \ 11 | --build-arg WIREMOCK_IMAGE_TAG=2.32.0-alpine \ 12 | --tag=sbermarkettech/grpc-wiremock:dev 13 | 14 | docker build . \ 15 | --build-arg GOLANG_IMAGE_REPO=registry.hub.docker.com \ 16 | --build-arg GOLANG_IMAGE_NAME=library/golang \ 17 | --build-arg GOLANG_IMAGE_TAG=1.21.7 \ 18 | --build-arg WIREMOCK_IMAGE_REPO=docker.io \ 19 | --build-arg WIREMOCK_IMAGE_NAME=wiremock/wiremock \ 20 | --build-arg WIREMOCK_IMAGE_TAG=2.32.0-alpine \ 21 | --tag=sbermarkettech/grpc-wiremock:dev 22 | -------------------------------------------------------------------------------- /pkg/wiremock/config/ports.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/sliceutils" 7 | ) 8 | 9 | type Ports []int 10 | 11 | func GatherPorts(wiremock Wiremock) Ports { 12 | var ports Ports 13 | 14 | for _, service := range wiremock.Services { 15 | ports = append(ports, service.Port) 16 | } 17 | 18 | sort.Slice(ports, func(i, j int) bool { 19 | return ports[i] < ports[j] 20 | }) 21 | 22 | return ports 23 | } 24 | 25 | func (p Ports) Allocate() int { 26 | const defaultPort = 8000 27 | 28 | if len(p) == 0 { 29 | return defaultPort 30 | } 31 | 32 | var ports Ports 33 | for _, port := range p { 34 | ports = append(ports, port) 35 | } 36 | 37 | sort.Slice(ports, func(i, j int) bool { 38 | return ports[i] > ports[j] 39 | }) 40 | 41 | return sliceutils.FirstOf(ports) + 1 42 | } 43 | -------------------------------------------------------------------------------- /pkg/models/basecontract/traverser/traverse.go: -------------------------------------------------------------------------------- 1 | package traverser 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jhump/protoreflect/desc" 7 | 8 | contract "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/basecontract" 9 | ) 10 | 11 | func Descriptors(contracts contract.SetOfContracts) ([]*desc.FileDescriptor, error) { 12 | var all []*desc.FileDescriptor 13 | 14 | for _, cont := range contracts { 15 | descriptor, err := contract.ProtoFromAny(cont) 16 | if err != nil { 17 | return nil, fmt.Errorf("from any: %w", err) 18 | } 19 | 20 | all = append(all, descriptor) 21 | rec(descriptor, &all) 22 | } 23 | 24 | return all, nil 25 | } 26 | 27 | func rec(desc *desc.FileDescriptor, all *[]*desc.FileDescriptor) { 28 | *all = append(*all, desc.GetDependencies()...) 29 | for _, descriptor := range desc.GetDependencies() { 30 | rec(descriptor, all) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /static/proxy/template/layout/internal/health/health.go: -------------------------------------------------------------------------------- 1 | package health 2 | 3 | import ( 4 | "context" 5 | 6 | "google.golang.org/grpc" 7 | 8 | "grpc-proxy/pkg/gitlab.sbmt.io/paas/health" 9 | ) 10 | 11 | type Service struct { 12 | health.UnimplementedHealthServer 13 | } 14 | 15 | func NewService() *Service { 16 | return &Service{} 17 | } 18 | 19 | func (s *Service) RegisterGRPC(server *grpc.Server) { 20 | health.RegisterHealthServer(server, s) 21 | } 22 | 23 | func (s *Service) Check(_ context.Context, req *health.HealthCheckRequest) (*health.HealthCheckResponse, error) { 24 | return &health.HealthCheckResponse{ 25 | Status: health.HealthCheckResponse_SERVING, 26 | }, nil 27 | } 28 | 29 | func (s *Service) Watch(_ *health.HealthCheckRequest, server health.Health_WatchServer) error { 30 | return server.Send(&health.HealthCheckResponse{ 31 | Status: health.HealthCheckResponse_SERVING, 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /benchmarks/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | wiremock: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile-wiremock 6 | 7 | environment: 8 | - WIREMOCK_API_COUNT=${WIREMOCK_API_COUNT} 9 | 10 | ports: 11 | - "8000-8100:8000-8100" 12 | 13 | volumes: 14 | - type: bind 15 | source: wiremock 16 | target: /home/wiremock 17 | 18 | healthcheck: 19 | test: ["CMD-SHELL", "/scripts/health.sh || exit 1"] 20 | interval: 1s 21 | timeout: 10s 22 | retries: 50 23 | 24 | benchmarks: 25 | build: 26 | context: . 27 | dockerfile: Dockerfile-benchmarks 28 | 29 | environment: 30 | - WIREMOCK_API_COUNT=${WIREMOCK_API_COUNT} 31 | 32 | volumes: 33 | - type: bind 34 | source: output 35 | target: /benchmarks/output 36 | 37 | depends_on: 38 | wiremock: 39 | condition: service_healthy 40 | -------------------------------------------------------------------------------- /pkg/jsonschema/schematomock/statuses_test.go: -------------------------------------------------------------------------------- 1 | package schematomock 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_getStatusCodes(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | definedStatuses map[int]struct{} 13 | statusCodeRaw string 14 | want []int 15 | }{ 16 | {definedStatuses: map[int]struct{}{}, statusCodeRaw: string(StatusCodeLevel5), want: statusCodesByLevel[StatusCodeLevel5]}, 17 | {statusCodeRaw: string(StatusCodeLevel5), want: statusCodesByLevel[StatusCodeLevel5]}, 18 | {definedStatuses: map[int]struct{}{101: {}, 103: {}}, statusCodeRaw: string(StatusCodeLevel1), want: []int{100, 102}}, 19 | } 20 | 21 | for _, tt := range tests { 22 | t.Run(tt.name, func(t *testing.T) { 23 | got, err := getStatusCodes(tt.definedStatuses, tt.statusCodeRaw) 24 | require.NoError(t, err) 25 | require.ElementsMatch(t, tt.want, got) 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/wiremock/client/wiremock.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/spf13/afero" 8 | 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/wiremock/config" 10 | ) 11 | 12 | type configOpener interface { 13 | Open() (config.Wiremock, error) 14 | } 15 | 16 | type wiremock struct { 17 | host string 18 | port uint 19 | 20 | client http.Client 21 | 22 | fs afero.Fs 23 | 24 | configOpener 25 | } 26 | 27 | func NewDefaultClient(fs afero.Fs, opener configOpener) *wiremock { 28 | var defaultTimeout = 15 * time.Second 29 | 30 | const ( 31 | defaultHost = "http://localhost" 32 | defaultPort = 9000 33 | ) 34 | 35 | return NewClient(fs, opener, defaultHost, defaultPort, http.Client{Timeout: defaultTimeout}) 36 | } 37 | 38 | func NewClient(fs afero.Fs, opener configOpener, host string, port uint, httpClient http.Client) *wiremock { 39 | return &wiremock{fs: fs, configOpener: opener, host: host, port: port, client: httpClient} 40 | } 41 | -------------------------------------------------------------------------------- /internal/usecases/certgen/generate.go: -------------------------------------------------------------------------------- 1 | package certgen 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/spf13/afero" 9 | 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/generators/certificates" 11 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/wiremock/configopener" 12 | ) 13 | 14 | type certsGen struct { 15 | supervisordPath string 16 | 17 | output string 18 | 19 | logger io.Writer 20 | 21 | afero.Fs 22 | } 23 | 24 | func NewCertsGenWithDefaultFs(output, supervisordPath string, logger io.Writer) certsGen { 25 | return certsGen{output: output, supervisordPath: supervisordPath, logger: logger, Fs: afero.NewOsFs()} 26 | } 27 | 28 | func (g *certsGen) Generate(ctx context.Context) error { 29 | opener := configopener.New(g.Fs, g.supervisordPath) 30 | generator := certificates.NewGenerator(g.Fs, opener) 31 | 32 | if err := generator.Generate(ctx, g.output); err != nil { 33 | return fmt.Errorf("generate: %w", err) 34 | } 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /pkg/fstesting/entrymock/entry_mock.go: -------------------------------------------------------------------------------- 1 | package entrymock 2 | 3 | import ( 4 | "io/fs" 5 | "path/filepath" 6 | "time" 7 | ) 8 | 9 | type Entry struct { 10 | isDir bool 11 | name string 12 | path string 13 | } 14 | 15 | func Dir(path string) Entry { 16 | return NewEntryMock(path, true) 17 | } 18 | 19 | func File(path string) Entry { 20 | return NewEntryMock(path, false) 21 | } 22 | 23 | func NewEntryMock(path string, isDir bool) Entry { 24 | return Entry{name: filepath.Base(path), isDir: isDir, path: path} 25 | } 26 | 27 | func (e Entry) Name() string { 28 | return e.name 29 | } 30 | 31 | func (e Entry) IsDir() bool { 32 | return e.isDir 33 | } 34 | 35 | func (e Entry) Path() string { 36 | return e.path 37 | } 38 | 39 | func (e Entry) Size() int64 { 40 | return 0 41 | } 42 | 43 | func (e Entry) Mode() fs.FileMode { 44 | return fs.ModePerm 45 | } 46 | 47 | func (e Entry) ModTime() time.Time { 48 | return time.Time{} 49 | } 50 | 51 | func (e Entry) Sys() any { 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /dev/example/test/wiremock/push-sender/mappings/post_push_sender_notify_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "c1fa0e22-a48a-4ea6-81a9-2c10e9a56107", 3 | "name" : "", 4 | "request" : { 5 | "urlPath" : "/PushSender/Notify", 6 | "method" : "POST" 7 | }, 8 | "response" : { 9 | "status" : 200, 10 | "body" : "{\n \"status\" : 42589510\n}", 11 | "headers" : { 12 | "Content-Type" : "application/json" 13 | } 14 | }, 15 | "uuid" : "c1fa0e22-a48a-4ea6-81a9-2c10e9a56107", 16 | "persistent" : true, 17 | "metadata" : { 18 | "mocklab" : { 19 | "created" : { 20 | "at" : "2023-02-07T10:14:25.348735653Z", 21 | "via" : "OAS3_IMPORT" 22 | }, 23 | "oas" : { 24 | "operationId" : "PushSender_Notify", 25 | "schema" : { 26 | "type" : "object", 27 | "properties" : { 28 | "status" : { 29 | "type" : "integer", 30 | "format" : "uint32" 31 | } 32 | } 33 | } 34 | } 35 | } 36 | }, 37 | "insertionIndex" : 0 38 | } -------------------------------------------------------------------------------- /benchmarks/output/report.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "min_response_time_api_count_2": 59, 4 | "max_response_time_api_count_2": 65, 5 | "avg_response_time_api_count_2": 62, 6 | "median_response_time_api_count_2": 65 7 | }, 8 | { 9 | "min_response_time_api_count_5": 122, 10 | "max_response_time_api_count_5": 161, 11 | "avg_response_time_api_count_5": 139, 12 | "median_response_time_api_count_5": 140 13 | }, 14 | { 15 | "min_response_time_api_count_20": 355, 16 | "max_response_time_api_count_20": 1149, 17 | "avg_response_time_api_count_20": 688.35, 18 | "median_response_time_api_count_20": 674 19 | }, 20 | { 21 | "min_response_time_api_count_10": 161, 22 | "max_response_time_api_count_10": 415, 23 | "avg_response_time_api_count_10": 275.4, 24 | "median_response_time_api_count_10": 300 25 | }, 26 | { 27 | "min_response_time_api_count_1": 31, 28 | "max_response_time_api_count_1": 31, 29 | "avg_response_time_api_count_1": 31, 30 | "median_response_time_api_count_1": 31 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /internal/usecases/watcher/setup_test.go: -------------------------------------------------------------------------------- 1 | package watcher 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func Test_getDomainByPath(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | path string 13 | want string 14 | }{ 15 | {path: "/home/mock/awesome/mappings/mock.json", want: "awesome"}, 16 | {path: "/home/mock/awesome-domain/mappings/mock.json", want: "awesome-domain"}, 17 | {path: "/home/mock/awesome-domain/__files/mock.json", want: "awesome-domain"}, 18 | {path: "/Users/test-user1/Desktop/awesome-project/deps/services/dependency/mappings/mock.json", want: "dependency"}, 19 | 20 | {path: "awesome/__files/file.ext", want: "awesome"}, 21 | {path: "/awesome/__files", want: "awesome"}, 22 | {path: "q_123-test-not-so(awesome/mappings", want: "q_123-test-not-so(awesome"}, 23 | } 24 | 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | got, err := getDomainByPath(tt.path) 28 | require.NoError(t, err) 29 | require.Equal(t, tt.want, got) 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/generators/mocks/mocks_test.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/mock" 9 | ) 10 | 11 | func Test_newName(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | 15 | mock mock.Mock 16 | 17 | want string 18 | }{ 19 | { 20 | mock: mock.Mock{RequestMethod: "POST", RequestUrlPath: "/Example/RpcName-1", ResponseStatusCode: 200}, 21 | want: "post_example_rpc_name_1_200.json", 22 | }, 23 | { 24 | mock: mock.Mock{RequestMethod: "GET", RequestUrlPath: "api_rpc_name", ResponseStatusCode: 404}, 25 | want: "get_api_rpc_name_404.json", 26 | }, 27 | { 28 | mock: mock.Mock{RequestMethod: "GET", RequestUrlPath: "api_rpc_name/domain/handler", ResponseStatusCode: 404}, 29 | want: "get_api_rpc_name_domain_handler_404.json", 30 | }, 31 | } 32 | 33 | for _, tt := range tests { 34 | t.Run(tt.name, func(t *testing.T) { 35 | got := createMockName(tt.mock) 36 | require.Equal(t, tt.want, got) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | if [ $# -ne 0 ]; then 6 | echo "executing: '$@'" 7 | exec $@ && exit 0 8 | fi 9 | 10 | SCRIPTS=$(realpath "$(dirname "${0}")") 11 | 12 | source "${SCRIPTS}/env.sh" 13 | 14 | # Change owner of directories. 15 | MOCK_CERTS_PATH="/etc/ssl/mock" 16 | LOGS_SUPERVISORD_PATH="/var/log/supervisord" 17 | 18 | USER_ID=1000 19 | 20 | sudo mkdir -p "${LOGS_SUPERVISORD_PATH}" 21 | 22 | sudo chown -R \ 23 | "${USER_ID}:${USER_ID}" \ 24 | "${GW_WIREMOCK_PATH}" \ 25 | "${GW_CONTRACTS_PATH}" \ 26 | "${LOGS_SUPERVISORD_PATH}" \ 27 | "${MOCK_CERTS_PATH}" 28 | 29 | # Setup logger. 30 | LOG="/var/log/wiremock" 31 | 32 | ## remove 'imklog' because no need to monitor kernel events. 33 | 34 | if [ "$(ps | grep '[r]syslogd')" == "" ]; then 35 | sudo sed -i '/^module.*imklog/s/^/#/' /etc/rsyslog.conf && sudo rsyslogd 36 | fi 37 | sudo touch "${LOG}" && tail -f ${LOG} & 38 | 39 | # Include utilities. 40 | source "${SCRIPTS}/other/log.sh" 41 | 42 | # Run grpc-wiremock entrypoint. 43 | source "${SCRIPTS}/entrypoint.sh" 44 | -------------------------------------------------------------------------------- /pkg/blacklist/blacklist.go: -------------------------------------------------------------------------------- 1 | package blacklist 2 | 3 | import "strings" 4 | 5 | func IsDeliveredWithProtoc(value string) bool { 6 | var ( 7 | filePathsDoNotTouch = []string{"google/protobuf"} 8 | goPackagesDoNotTouch = []string{"google.golang.org/protobuf"} 9 | ) 10 | 11 | for _, file := range filePathsDoNotTouch { 12 | if strings.Contains(value, file) { 13 | return true 14 | } 15 | } 16 | 17 | for _, goPackage := range goPackagesDoNotTouch { 18 | if strings.Contains(value, goPackage) { 19 | return true 20 | } 21 | } 22 | 23 | return false 24 | } 25 | 26 | func IsGoogleAPIContract(value string) bool { 27 | var ( 28 | filePathsDoNotTouch = []string{"google/api"} 29 | goPackagesDoNotTouch = []string{"google.golang.org/genproto/googleapis/api"} 30 | ) 31 | 32 | for _, file := range filePathsDoNotTouch { 33 | if strings.Contains(value, file) { 34 | return true 35 | } 36 | } 37 | 38 | for _, goPackage := range goPackagesDoNotTouch { 39 | if strings.Contains(value, goPackage) { 40 | return true 41 | } 42 | } 43 | 44 | return false 45 | } 46 | -------------------------------------------------------------------------------- /pkg/fstesting/setup.go: -------------------------------------------------------------------------------- 1 | package fstesting 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/spf13/afero" 9 | 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/fstesting/entrymock" 11 | ) 12 | 13 | func CreateMockFS(entries ...entrymock.Entry) afero.Fs { 14 | fs := afero.NewMemMapFs() 15 | 16 | // the MemMapFS write error is deliberately ignored 17 | fs, _ = WriteMockEntries(fs, entries...) 18 | 19 | return fs 20 | } 21 | 22 | func WriteMockEntries(fs afero.Fs, entries ...entrymock.Entry) (afero.Fs, error) { 23 | for _, entry := range entries { 24 | path := entry.Path() 25 | 26 | if entry.IsDir() { 27 | if err := fs.MkdirAll(path, os.ModePerm); err != nil { 28 | return nil, fmt.Errorf("create dir: %w", err) 29 | } 30 | } else { 31 | if err := fs.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { 32 | return nil, fmt.Errorf("create dir: %w", err) 33 | } 34 | _, err := fs.Create(path) 35 | if err != nil { 36 | return nil, fmt.Errorf("create file: %w", err) 37 | } 38 | } 39 | } 40 | 41 | return fs, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/utils/strutils/golang.go: -------------------------------------------------------------------------------- 1 | package strutils 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Data types are allowed to be used as a code identifiers. 9 | // That's why there keywords only. 10 | var golangKeywords = map[string]struct{}{ 11 | "break": {}, "default": {}, "func": {}, "interface": {}, "select": {}, 12 | "case": {}, "defer": {}, "go": {}, "map": {}, "struct": {}, "chan": {}, "else": {}, 13 | "goto": {}, "package": {}, "switch": {}, "const": {}, "fallthrough": {}, "if": {}, 14 | "continue": {}, "for": {}, "import": {}, "return": {}, "var": {}, "type": {}, 15 | } 16 | 17 | func ResolveNameIfCollides(keyword string) string { 18 | if isGolangKeyword(keyword) { 19 | return resolve(keyword) 20 | } 21 | 22 | return keyword 23 | } 24 | 25 | func isGolangKeyword(keyword string) bool { 26 | value := strings.ToLower(keyword) 27 | _, isExist := golangKeywords[value] 28 | 29 | return isExist 30 | } 31 | 32 | func resolve(value string) string { 33 | const suffixToResolveCollision = "resolved" 34 | return fmt.Sprintf("%s_%s", value, suffixToResolveCollision) 35 | } 36 | -------------------------------------------------------------------------------- /pkg/models/protocontract/loader/proto.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/afero" 7 | 8 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/protocontract" 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/protocontract/parser" 10 | ) 11 | 12 | type loader struct { 13 | fs afero.Fs 14 | 15 | source contractSourcer 16 | } 17 | 18 | func NewLoader(fs afero.Fs, source contractSourcer) loader { 19 | return loader{fs: fs, source: source} 20 | } 21 | 22 | func (l loader) LoadFile() (protocontract.SetOfContracts, error) { 23 | protoFiles := l.source.SourceInputs() 24 | 25 | contracts, err := parser.ParseProtoFiles(protoFiles) 26 | if err != nil { 27 | return nil, fmt.Errorf("parse proto file: %w", err) 28 | } 29 | 30 | return contracts, nil 31 | } 32 | 33 | func (l loader) LoadDir() (protocontract.SetOfContracts, error) { 34 | protoPaths := l.source.SourceInputs() 35 | 36 | contracts, err := parser.ParseProtoDir(l.fs, protoPaths) 37 | if err != nil { 38 | return nil, fmt.Errorf("parse proto dir: %w", err) 39 | } 40 | 41 | return contracts, nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/utils/sliceutils/sliceutils_test.go: -------------------------------------------------------------------------------- 1 | package sliceutils 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFirstOf(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | 14 | slice []string 15 | 16 | want string 17 | }{ 18 | {slice: []string{"foo", "bar"}, want: "foo"}, 19 | {slice: []string{}, want: ""}, 20 | } 21 | 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | got := FirstOf(tt.slice) 25 | assert.Equal(t, tt.want, got) 26 | }) 27 | } 28 | } 29 | 30 | func TestSliceToMap(t *testing.T) { 31 | type testCase[T comparable] struct { 32 | name string 33 | 34 | slice []T 35 | 36 | want map[T]struct{} 37 | } 38 | tests := []testCase[string]{ 39 | {slice: []string{"1", "1", "2"}, want: map[string]struct{}{"1": {}, "2": {}}}, 40 | {slice: []string{""}, want: map[string]struct{}{"": {}}}, 41 | } 42 | 43 | for _, tt := range tests { 44 | t.Run(tt.name, func(t *testing.T) { 45 | got := SliceToMap(tt.slice) 46 | assert.True(t, reflect.DeepEqual(tt.want, got)) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /etc/nginx/http.d/default.conf: -------------------------------------------------------------------------------- 1 | # This is a default site configuration which will simply return 404 with explanation, 2 | # preventing chance access to any other virtualhost. 3 | 4 | log_format not_mocked 5 | 'warn: domain "$host" is not mocked' 6 | ' - [$time_local] $remote_addr "$http_user_agent" "$request" '; 7 | 8 | server_names_hash_bucket_size 128; 9 | 10 | server { 11 | listen 80 default_server; 12 | listen [::]:80 default_server; 13 | listen 443 ssl; 14 | 15 | ssl_certificate /etc/ssl/mock/mock.crt; 16 | ssl_certificate_key /etc/ssl/mock/mock.key; 17 | 18 | return 404 'Domain "$host" is not mocked.\nFollow manual to mock it: `TODO`\n'; 19 | 20 | access_log /var/log/nginx/not_mocked.log not_mocked; 21 | } 22 | 23 | server { 24 | listen 3009 ssl http2; 25 | 26 | ssl_certificate /etc/ssl/mock/mock.crt; 27 | ssl_certificate_key /etc/ssl/mock/mock.key; 28 | 29 | location / { 30 | grpc_set_header Host $host; 31 | grpc_set_header X-Real-IP $remote_addr; 32 | grpc_pass grpc://localhost:3010; 33 | } 34 | 35 | access_log /var/log/nginx/grpc_reverse_proxy.log main; 36 | } 37 | -------------------------------------------------------------------------------- /static/tests/data/openapi/petstore/users.yaml: -------------------------------------------------------------------------------- 1 | openapi: "3.0.0" 2 | info: 3 | version: 1.0.0 4 | title: Users 5 | paths: 6 | /users: 7 | get: 8 | operationId: listUsers 9 | responses: 10 | '200': 11 | description: user response 12 | content: 13 | application/json: 14 | schema: 15 | type: array 16 | items: 17 | $ref: '#/components/schemas/User' 18 | default: 19 | description: unexpected error 20 | content: 21 | application/json: 22 | schema: 23 | $ref: '#/components/schemas/Error' 24 | components: 25 | schemas: 26 | User: 27 | type: object 28 | properties: 29 | id: 30 | type: integer 31 | format: int64 32 | name: 33 | type: string 34 | required: 35 | - id 36 | - name 37 | Error: 38 | type: object 39 | required: 40 | - code 41 | - message 42 | properties: 43 | code: 44 | type: integer 45 | format: int32 46 | message: 47 | type: string 48 | -------------------------------------------------------------------------------- /static/tests/data/openapi/users-merged/users.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | components: 3 | schemas: 4 | Error: 5 | type: object 6 | required: 7 | - code 8 | - message 9 | properties: 10 | code: 11 | type: integer 12 | format: int32 13 | message: 14 | type: string 15 | User: 16 | type: object 17 | required: 18 | - id 19 | - name 20 | properties: 21 | id: 22 | type: integer 23 | format: int64 24 | name: 25 | type: string 26 | info: 27 | title: Users 28 | version: 1.0.0 29 | paths: 30 | /users: 31 | get: 32 | operationId: listUsers 33 | responses: 34 | "200": 35 | description: user response 36 | content: 37 | application/json: 38 | schema: 39 | type: array 40 | items: 41 | $ref: '#/components/schemas/User' 42 | default: 43 | description: unexpected error 44 | content: 45 | application/json: 46 | schema: 47 | $ref: '#/components/schemas/Error' 48 | -------------------------------------------------------------------------------- /pkg/utils/testutils/testing_test.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/spf13/afero" 7 | "github.com/stretchr/testify/require" 8 | 9 | "github.com/SberMarket-Tech/grpc-wiremock/static" 10 | ) 11 | 12 | var fs = static.FromEmbed() 13 | 14 | func TestReadTestsStatuses(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | fs afero.Fs 18 | path string 19 | want TestToCases 20 | }{ 21 | { 22 | fs: fs, 23 | path: "tests-statuses.yml", 24 | want: map[string]testCasesMaps{ 25 | "generation": { 26 | Skip: map[string]struct{}{"simple-1-flaky": {}}, 27 | Fail: map[string]struct{}{"simple-2-fail": {}}, 28 | Success: map[string]struct{}{"simple-success": {}}, 29 | }, 30 | "run-some-fancy-test-name": { 31 | Skip: map[string]struct{}{"flaky-test-1": {}}, 32 | Fail: map[string]struct{}{}, 33 | Success: map[string]struct{}{}, 34 | }, 35 | }, 36 | }, 37 | } 38 | for _, tt := range tests { 39 | t.Run(tt.name, func(t *testing.T) { 40 | got, err := ReadTestsStatuses(tt.fs, tt.path) 41 | require.NoError(t, err) 42 | 43 | require.Equal(t, tt.want, got) 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /static/tests/data/openapi/petstore-and-users-merged/users.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | components: 3 | schemas: 4 | Error: 5 | type: object 6 | required: 7 | - code 8 | - message 9 | properties: 10 | code: 11 | type: integer 12 | format: int32 13 | message: 14 | type: string 15 | User: 16 | type: object 17 | required: 18 | - id 19 | - name 20 | properties: 21 | id: 22 | type: integer 23 | format: int64 24 | name: 25 | type: string 26 | info: 27 | title: Users 28 | version: 1.0.0 29 | paths: 30 | /users: 31 | get: 32 | operationId: listUsers 33 | responses: 34 | "200": 35 | description: user response 36 | content: 37 | application/json: 38 | schema: 39 | type: array 40 | items: 41 | $ref: '#/components/schemas/User' 42 | default: 43 | description: unexpected error 44 | content: 45 | application/json: 46 | schema: 47 | $ref: '#/components/schemas/Error' 48 | -------------------------------------------------------------------------------- /cmd/certgen/commands/certgen.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/SberMarket-Tech/grpc-wiremock/internal/usecases/certgen" 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/environment" 11 | ) 12 | 13 | type CertgenArgs struct { 14 | supervisordPath string 15 | 16 | output string 17 | } 18 | 19 | func certgenCommand(subCommands ...*cobra.Command) *cobra.Command { 20 | var args CertgenArgs 21 | 22 | command := &cobra.Command{ 23 | Use: "certgen", 24 | RunE: func(cmd *cobra.Command, _ []string) error { 25 | generator := certgen.NewCertsGenWithDefaultFs(args.output, args.supervisordPath, os.Stdout) 26 | 27 | if err := generator.Generate(cmd.Context()); err != nil { 28 | return fmt.Errorf("generate certs: %w", err) 29 | } 30 | 31 | return nil 32 | }, 33 | } 34 | 35 | command.Flags().StringVar(&args.supervisordPath, "supervisord-path", environment.SupervisordConfigsDirPath, "Directory with Supervisord config") 36 | command.Flags().StringVar(&args.output, "output", environment.DefaultCertificatesPath, "Directory with certificates") 37 | 38 | command.AddCommand(subCommands...) 39 | 40 | return command 41 | } 42 | -------------------------------------------------------------------------------- /dev/example/Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash -euo pipefail 2 | 3 | MOCKS_PATH="$(shell pwd)/test/wiremock" 4 | CERTS_PATH="$(shell pwd)/certs" 5 | CONTRACTS_PATH="$(shell pwd)/deps" 6 | 7 | export GRPC_WIREMOCK_IMAGE=sbermarkettech/grpc-wiremock:dev 8 | 9 | compose-up: 10 | source build/.env && \ 11 | docker compose -f build/docker-compose.yaml up -d 12 | 13 | compose-exec: 14 | @echo -e "Entering to 'wearable-mock' container\nTo develop run ./dev/watch_wiremock.sh\n" 15 | @source build/.env && \ 16 | docker compose -f build/docker-compose.yaml exec -it --workdir=/source wearable-mock bash 17 | 18 | compose-logs: 19 | source build/.env && \ 20 | docker compose -f build/docker-compose.yaml logs -f --tail=50 21 | 22 | compose-up-recreate: 23 | source build/.env && \ 24 | docker compose -f build/docker-compose.yaml up -d --force-recreate --build 25 | 26 | compose-down-all: 27 | source build/.env && \ 28 | docker compose -f build/docker-compose.yaml down --rmi all 29 | 30 | docker-run: 31 | docker run \ 32 | -p 9000:9000 \ 33 | -p 8000:8000 \ 34 | -v ${MOCKS_PATH}:/home/mock \ 35 | -v ${CERTS_PATH}:/etc/ssl/mock/share \ 36 | -v ${CONTRACTS_PATH}:/contracts \ 37 | ${GRPC_WIREMOCK_IMAGE} 38 | -------------------------------------------------------------------------------- /static/proto-annotations/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } 32 | -------------------------------------------------------------------------------- /scripts/other/check_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | echo "===== Dependencies check is started..." 6 | 7 | if command -v protoc > /dev/null; then 8 | echo "- proto compiler [OK]" 9 | else 10 | echo "- proto compiler [FAIL]" 11 | echo "To install proto compiler: https://grpc.io/docs/protoc-installation" 12 | fi 13 | 14 | GO="protoc-gen-go" 15 | if command -v ${GO} > /dev/null; then 16 | echo "- ${GO} plugin [OK]" 17 | else 18 | echo "- ${GO} plugin [FAIL]" 19 | echo "To install ${GO} plugin:" 20 | echo "- go install google.golang.org/protobuf/cmd/${GO}@latest" 21 | fi 22 | 23 | GO_GRPC="protoc-gen-go-grpc" 24 | if command -v ${GO_GRPC} > /dev/null; then 25 | echo "- ${GO_GRPC} plugin [OK]" 26 | else 27 | echo "- ${GO_GRPC} plugin [FAIL]" 28 | echo "To install ${GO_GRPC} plugin:" 29 | echo "- go install google.golang.org/grpc/cmd/${GO_GRPC}@latest" 30 | fi 31 | 32 | OPENAPI="protoc-gen-openapi" 33 | if command -v ${OPENAPI} > /dev/null; then 34 | echo "- ${OPENAPI} plugin [OK]" 35 | else 36 | echo "- ${OPENAPI} plugin [FAIL]" 37 | echo "To install ${OPENAPI} plugin:" 38 | echo "- https://github.com/solo-io/${OPENAPI}" 39 | fi 40 | 41 | echo "===== Dependencies check is done." -------------------------------------------------------------------------------- /scripts/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # User's host directories mounted as volume. 6 | export GW_WIREMOCK_PATH="/home/mock" 7 | export GW_CONTRACTS_PATH="/contracts" 8 | export GW_CERTS_PATH="/etc/ssl/mock/share" 9 | 10 | SCRIPTS=$(realpath "$(dirname "${BASH_SOURCE[0]}")") 11 | 12 | # grpc-wiremock setup scripts variables. 13 | export SCRIPTS="${SCRIPTS}" 14 | export MOCKS="${SCRIPTS}/mocks" 15 | export PROXY="${SCRIPTS}/proxy" 16 | export MULTIAPI="${SCRIPTS}/multiapi" 17 | export OTHER="${SCRIPTS}/other" 18 | export ROUTING="${SCRIPTS}/routing" 19 | export CERTS="${SCRIPTS}/routing/certs" 20 | 21 | export WIREMOCK_ADDR="localhost:9000" 22 | 23 | # Headers for rsyslog. 24 | export ENTRYPOINT_HEADER="gw.entrypoint" 25 | export WIREMOCK_RUN_HEADER="gw.wiremock.run" 26 | export PROXY_GEN_HEADER="gw.proxy.gen" 27 | export PROXY_WATCH_HEADER="gw.proxy.watch" 28 | export MOCKS_GEN_HEADER="gw.mocks.gen" 29 | export MOCKS_WATCH_HEADER="gw.mocks.watch" 30 | export ROUTING_CERTS_GEN_HEADER="gw.routing.certs.gen" 31 | export ROUTING_NGINX_GEN_HEADER="gw.routing.nginx.gen" 32 | export ROUTING_NGINX_WATCH_HEADER="gw.routing.nginx.watch" 33 | export ROUTING_NGINX_LOGS_HEADER="gw.routing.nginx.logs" 34 | export MULTIAPI_LOGS_HEADER="gw.multiapi.supervisord.logs" 35 | -------------------------------------------------------------------------------- /pkg/models/protocontract/contract_test.go: -------------------------------------------------------------------------------- 1 | package protocontract 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/sourcer/types" 9 | ) 10 | 11 | func TestSourceFileTypeFromPath(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | path string 15 | want types.SourceFileType 16 | }{ 17 | {path: "/some/test/path/file.proto", want: types.ProtoType}, 18 | {path: "/some/test/path/file.yaml", want: types.OpenAPIType}, 19 | {path: "/some/test/path/file.yml", want: ""}, 20 | } 21 | 22 | for _, tt := range tests { 23 | t.Run(tt.name, func(t *testing.T) { 24 | got := types.SourceFileTypeFromPath(tt.path) 25 | assert.Equal(t, tt.want, got) 26 | }) 27 | } 28 | } 29 | 30 | func TestSourceFileType_Is(t *testing.T) { 31 | tests := []struct { 32 | name string 33 | s types.SourceFileType 34 | that string 35 | want bool 36 | }{ 37 | {s: types.OpenAPIType, that: "yaml", want: true}, 38 | {s: types.ProtoType, that: "proto", want: true}, 39 | {s: types.OpenAPIType, that: "yml", want: false}, 40 | } 41 | 42 | for _, tt := range tests { 43 | t.Run(tt.name, func(t *testing.T) { 44 | got := tt.s.Is(tt.that) 45 | assert.Equal(t, tt.want, got) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /dev/example/internal/wearable_service/service.go: -------------------------------------------------------------------------------- 1 | package wearable_service 2 | 3 | import ( 4 | "fmt" 5 | 6 | "google.golang.org/grpc" 7 | "google.golang.org/grpc/credentials/insecure" 8 | 9 | "github.com/nktch1/wearable/pkg/clients/push_sender" 10 | "github.com/nktch1/wearable/pkg/server/wearable" 11 | ) 12 | 13 | type Service struct { 14 | wearable.UnimplementedWearableServiceServer 15 | sender push_sender.PushSenderClient 16 | } 17 | 18 | func NewService() (*Service, error) { 19 | addr := "wearable-mock:3010" 20 | 21 | client, err := createClient(addr) 22 | if err != nil { 23 | return nil, fmt.Errorf("create push sender client: %w", err) 24 | } 25 | 26 | return &Service{sender: client}, nil 27 | } 28 | 29 | func (p *Service) RegisterGRPC(server *grpc.Server) { 30 | wearable.RegisterWearableServiceServer(server, p) 31 | } 32 | 33 | func createClient(address string) (push_sender.PushSenderClient, error) { 34 | opts := []grpc.DialOption{ 35 | grpc.WithTransportCredentials(insecure.NewCredentials()), 36 | 37 | // add authority to grpc client 38 | grpc.WithAuthority("push-sender"), 39 | } 40 | 41 | conn, err := grpc.Dial(address, opts...) 42 | if err != nil { 43 | return nil, fmt.Errorf("create push sender connect: %w", err) 44 | } 45 | 46 | return push_sender.NewPushSenderClient(conn), nil 47 | } 48 | -------------------------------------------------------------------------------- /cmd/mockgen/commands/mockgen-first-run.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | 9 | mockgen_first_run "github.com/SberMarket-Tech/grpc-wiremock/internal/usecases/mockgen-first-run" 10 | ) 11 | 12 | type MockgenFirstRunArgs struct { 13 | DomainsPath string 14 | WiremockPath string 15 | } 16 | 17 | func mockgenFirstRun(subCommands ...*cobra.Command) *cobra.Command { 18 | var args MockgenFirstRunArgs 19 | 20 | command := &cobra.Command{ 21 | Use: "first-run", 22 | RunE: func(cmd *cobra.Command, _ []string) error { 23 | gen := mockgen_first_run.NewMocksGenWithDefaultFs(args.DomainsPath, args.WiremockPath, os.Stdout) 24 | return gen.GenerateForEachDomain(cmd.Context()) 25 | }, 26 | } 27 | 28 | command.Flags().StringVar(&args.DomainsPath, "domains-path", "", "Directory with domain directories") 29 | command.Flags().StringVar(&args.WiremockPath, "wiremock-path", "", "Directory with Wiremock config") 30 | 31 | if err := command.MarkFlagRequired("domains-path"); err != nil { 32 | log.Fatalf("mark flag 'domains-path' required: %s", err) 33 | } 34 | 35 | if err := command.MarkFlagRequired("wiremock-path"); err != nil { 36 | log.Fatalf("mark flag 'wiremock-path' required: %s", err) 37 | } 38 | 39 | command.AddCommand(subCommands...) 40 | 41 | return command 42 | } 43 | -------------------------------------------------------------------------------- /pkg/models/basecontract/loader/proto/proto.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/afero" 7 | 8 | contract "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/basecontract" 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/basecontract/parser/proto" 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/sourcer/types" 11 | ) 12 | 13 | type contractSourcer interface { 14 | SourceInputs() []string 15 | SourceLoadType() types.SourceLoadType 16 | SourceFileType() types.SourceFileType 17 | } 18 | 19 | type loader struct { 20 | fs afero.Fs 21 | source contractSourcer 22 | } 23 | 24 | func NewLoader(fs afero.Fs, source contractSourcer) loader { 25 | return loader{fs: fs, source: source} 26 | } 27 | 28 | func (l loader) LoadFile() (contract.SetOfContracts, error) { 29 | protoFiles := l.source.SourceInputs() 30 | 31 | contracts, err := proto.ParseProtoFiles(protoFiles) 32 | if err != nil { 33 | return nil, fmt.Errorf("parse proto file: %w", err) 34 | } 35 | 36 | return contracts, nil 37 | } 38 | 39 | func (l loader) LoadDir() (contract.SetOfContracts, error) { 40 | protoPaths := l.source.SourceInputs() 41 | 42 | contracts, err := proto.ParseProtoDir(l.fs, protoPaths) 43 | if err != nil { 44 | return nil, fmt.Errorf("parse proto dir: %w", err) 45 | } 46 | 47 | return contracts, nil 48 | } 49 | -------------------------------------------------------------------------------- /dev/example/certs/mockCA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDezCCAmOgAwIBAgICBGYwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UEBhMCUlUx 3 | DzANBgNVBAcTBk1vc2NvdzEXMBUGA1UEChMOU2Jlcm1hcmtldCBMTEMxFjAUBgNV 4 | BAMTDWdycGMtd2lyZW1vY2swHhcNMjQwMzA0MDc1MTA0WhcNMjUwMzA0MDc1MTA0 5 | WjBPMQswCQYDVQQGEwJSVTEPMA0GA1UEBxMGTW9zY293MRcwFQYDVQQKEw5TYmVy 6 | bWFya2V0IExMQzEWMBQGA1UEAxMNZ3JwYy13aXJlbW9jazCCASIwDQYJKoZIhvcN 7 | AQEBBQADggEPADCCAQoCggEBAOvQ9hhDMwrKQ9BY/WYFiB7oVopJlDrJVeYqELr9 8 | 5AZEm/0qva7qzUekfoVoaBK8FWEM0I7WyXfI8bZu1U+/bcqCQiTD0WAAPncDIUPO 9 | x0CYPOrMo0hsT6EKvHa5F7wGDmjQ/2Oqpr18QKtxjFK7vgFMevY26X0TXh6gSA6H 10 | cjc12o2a920IMG9xaq1qsyxql3hZEgntgcKlXSM+niv96/WZLs5tmyEpGldSz6rj 11 | /gVw6By2IY+YASh/LDHzo3velnOZCOgFlDmtfvDDwAT9ZPc8Xf4PCIoCwvX5HJBA 12 | oJAn9+x/QqtZn4QAQg4C0qGFDY9DLZTwCS5Nj3YnpRXSyKMCAwEAAaNhMF8wDgYD 13 | VR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNV 14 | HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQL0a57vnuax0QAtKOhOKhKI7dVsTANBgkq 15 | hkiG9w0BAQsFAAOCAQEAZwItYJ57hpJFq5FMCZBJyN6CnrOoOsysTrbfewaLsEZz 16 | yGTz4MV8hNK5WZ3Mim+Jkkp4XkZKx0ROJ4CeNCgJpqYGKFNna6XMMcgT9Vlnwf0E 17 | oOdAq+m28cqq7W9gU3AROixU6EQVwAQzA/tY32HxW9cAAeUAbZ52wsuXU7/sMbhl 18 | N6NbmaTdnh+DBSloV3uH2UsvI1SvuvE9qKC2Ad8WhQfJAOKl/O477W8JudRN+W3m 19 | oU2Ay49RSg6CXCVjBb2Y0crNqBuU90SCjybyGF/W3HxXadIJb+9VhHHbBGp2xHeG 20 | ltDKhY6EYG5s4ja0maqjnAc2TlKbhiq5SC+hXXm2Aw== 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /pkg/utils/httputils/request.go: -------------------------------------------------------------------------------- 1 | package httputils 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net/http" 9 | 10 | "github.com/avast/retry-go/v4" 11 | ) 12 | 13 | const defaultAttemptsValue = 7 14 | 15 | func DoPost(ctx context.Context, client http.Client, url string, reader io.Reader) (int, error) { 16 | httpRequest, err := http.NewRequest(http.MethodPost, url, reader) 17 | if err != nil { 18 | return 0, fmt.Errorf("new request: %w", err) 19 | } 20 | 21 | httpRequest = httpRequest.WithContext(ctx) 22 | 23 | var httpResponse *http.Response 24 | 25 | err = retry.Do(func() error { 26 | httpResponse, err = client.Do(httpRequest) 27 | if err != nil { 28 | return fmt.Errorf("do: %w", err) 29 | } 30 | 31 | return nil 32 | }, retry.Attempts(defaultAttemptsValue)) 33 | 34 | if err != nil { 35 | return 0, fmt.Errorf("retry: %w", err) 36 | } 37 | 38 | if httpResponse == nil { 39 | return 0, fmt.Errorf("response is empty") 40 | } 41 | 42 | defer func() { 43 | if err = httpResponse.Body.Close(); err != nil { 44 | log.Println("close body:", err) 45 | } 46 | }() 47 | 48 | return httpResponse.StatusCode, nil 49 | } 50 | 51 | func AssertStatus(expected, actual int) error { 52 | if actual != expected { 53 | return fmt.Errorf("expected status: %d, actual status: %d", expected, actual) 54 | } 55 | 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /static/proxy/template/layout/pkg/status/status.go: -------------------------------------------------------------------------------- 1 | package statustocode 2 | 3 | import ( 4 | "net/http" 5 | 6 | "google.golang.org/grpc/codes" 7 | ) 8 | 9 | func GetCodeFromResponse(response *http.Response) codes.Code { 10 | if response == nil { 11 | return codes.Unknown 12 | } 13 | return codeFromHTTPStatus(response.StatusCode) 14 | } 15 | 16 | func GetStatusFromResponse(response *http.Response) int { 17 | if response == nil { 18 | return http.StatusNotFound 19 | } 20 | return response.StatusCode 21 | } 22 | 23 | func codeFromHTTPStatus(code int) codes.Code { 24 | switch code { 25 | case http.StatusBadRequest: 26 | return codes.InvalidArgument 27 | case http.StatusUnauthorized: 28 | return codes.Unauthenticated 29 | case http.StatusForbidden: 30 | return codes.PermissionDenied 31 | case http.StatusNotFound: 32 | return codes.NotFound 33 | case http.StatusConflict: 34 | return codes.AlreadyExists 35 | case http.StatusTooManyRequests: 36 | return codes.ResourceExhausted 37 | case http.StatusInternalServerError: 38 | return codes.Internal 39 | case http.StatusNotImplemented: 40 | return codes.Unimplemented 41 | case http.StatusServiceUnavailable: 42 | return codes.Unavailable 43 | case http.StatusGatewayTimeout: 44 | return codes.DeadlineExceeded 45 | case http.StatusOK: 46 | return codes.OK 47 | } 48 | return codes.Unknown 49 | } 50 | -------------------------------------------------------------------------------- /internal/usecases/watcher/watch.go: -------------------------------------------------------------------------------- 1 | package watcher 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/watcher" 9 | ) 10 | 11 | type watchersRunner struct { 12 | logger io.Writer 13 | } 14 | 15 | func NewRunner(logger io.Writer) watchersRunner { 16 | return watchersRunner{logger: logger} 17 | } 18 | 19 | func (r *watchersRunner) Watch(ctx context.Context, requests ...WatchRequest) error { 20 | watchers, err := r.createWatchers(requests) 21 | if err != nil { 22 | return fmt.Errorf("create watchers: %w", err) 23 | } 24 | 25 | if err = watchers.Watch(ctx); err != nil { 26 | return fmt.Errorf("watch: %w", err) 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func (r *watchersRunner) createWatchers(requests []WatchRequest) (watcher.Watchers, error) { 33 | var watchers watcher.Watchers 34 | 35 | for _, request := range requests { 36 | preDefinedWatcher, exists := knownWatchers[request.Name] 37 | if !exists { 38 | return nil, fmt.Errorf("watcher '%s' is not found", request.Name) 39 | } 40 | 41 | preDefinedWatcher.Path = request.Path 42 | 43 | realWatcher, err := watcher.NewRealWatcher(preDefinedWatcher, r.logger) 44 | if err != nil { 45 | return nil, fmt.Errorf("create watcher: %w", err) 46 | } 47 | 48 | watchers = append(watchers, realWatcher) 49 | } 50 | 51 | return watchers, nil 52 | } 53 | -------------------------------------------------------------------------------- /static/proxy/files/method-unary.go.tpl: -------------------------------------------------------------------------------- 1 | package {{ .PackageHeader }} 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "net/http" 8 | 9 | "google.golang.org/grpc/status" 10 | "google.golang.org/protobuf/encoding/protojson" 11 | 12 | "grpc-proxy/pkg/wiremock" 13 | 14 | {{ range .GoPackages }} 15 | "{{ . }}" 16 | {{- end }} 17 | ) 18 | 19 | func (p *Service) {{ .Method }}(ctx context.Context, in *{{ .MethodInPackage }}.{{ .MethodInName }}) (*{{ .MethodOutPackage }}.{{ .MethodOutName }}, error) { 20 | const url = "{{ .URL }}" 21 | 22 | requestBody, err := protojson.Marshal(in) 23 | if err != nil { 24 | return nil, status.Error(http.StatusBadGateway, fmt.Sprintf("create http request body: %v", err)) 25 | } 26 | 27 | request, err := wiremock.DefaultRequest(ctx, url, bytes.NewReader(requestBody)) 28 | if err != nil { 29 | return nil, status.Error(http.StatusBadGateway, fmt.Sprintf("create http request: %v", err)) 30 | } 31 | 32 | httpResponseBody, err := wiremock.DoRequestDefault(request) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | var protoResponse {{ .MethodOutPackage }}.{{ .MethodOutName }} 38 | if err = protojson.Unmarshal(httpResponseBody, &protoResponse); err != nil { 39 | return nil, status.Error(http.StatusBadGateway, fmt.Sprintf("marshal json object to proto: %v", err)) 40 | } 41 | 42 | return &protoResponse, nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/generators/configs/nginx/configure.go: -------------------------------------------------------------------------------- 1 | package nginx 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | 7 | "github.com/spf13/afero" 8 | 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/generators/configs" 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/fsutils" 11 | ) 12 | 13 | // renderer abstracts how exactly project should be rendered. 14 | type renderer interface { 15 | Substitute(string, interface{}) (string, error) 16 | } 17 | 18 | type nginxDomainConfView struct { 19 | Domain string 20 | Port string 21 | } 22 | 23 | type Configuer struct { 24 | Renderer renderer 25 | OutputPath string 26 | 27 | afero.Fs 28 | } 29 | 30 | func (c Configuer) GenerateConfig(values configs.Values) error { 31 | const templatePath = "proxy-nginx/files/nginx.conf.tpl" 32 | 33 | confView := nginxDomainConfView{Domain: values.Domain, Port: values.Port} 34 | 35 | content, err := c.Renderer.Substitute(templatePath, &confView) 36 | if err != nil { 37 | return fmt.Errorf("substitute: %w", err) 38 | } 39 | 40 | pathToSave := c.createConfigPath(values.Domain) 41 | 42 | if err = fsutils.WriteFile(c.Fs, pathToSave, content); err != nil { 43 | return fmt.Errorf("write: %w", err) 44 | } 45 | 46 | return nil 47 | } 48 | 49 | func (c Configuer) createConfigPath(domain string) string { 50 | return filepath.Join(c.OutputPath, fmt.Sprintf("mock-%s.conf", domain)) 51 | } 52 | -------------------------------------------------------------------------------- /pkg/wiremock/client/mocks.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/httputils" 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/wiremock/config" 10 | ) 11 | 12 | func (w *wiremock) UpdateMocks(ctx context.Context, domain string) error { 13 | port, err := w.findPortByDomain(domain) 14 | if err != nil { 15 | return fmt.Errorf("find port by domain '%s': %w", domain, err) 16 | } 17 | 18 | if err = w.resetMocks(ctx, port); err != nil { 19 | return fmt.Errorf("reset mocks: %w", err) 20 | } 21 | 22 | return nil 23 | } 24 | 25 | func (w *wiremock) resetMocks(ctx context.Context, port uint) error { 26 | const path = "__admin/mappings/reset" 27 | 28 | url := fmt.Sprintf("%s:%d/%s", w.host, port, path) 29 | 30 | status, err := httputils.DoPost(ctx, w.client, url, nil) 31 | if err != nil { 32 | return fmt.Errorf("request: %w", err) 33 | } 34 | 35 | return httputils.AssertStatus(http.StatusOK, status) 36 | } 37 | 38 | func (w *wiremock) findPortByDomain(domain string) (uint, error) { 39 | wiremockConfig, err := w.configOpener.Open() 40 | if err != nil { 41 | return 0, fmt.Errorf("open: %w", err) 42 | } 43 | 44 | for _, service := range wiremockConfig.Services { 45 | if service.Name == domain { 46 | return uint(service.Port), nil 47 | } 48 | } 49 | 50 | return 0, config.NoCorrespondingAPIErr 51 | } 52 | -------------------------------------------------------------------------------- /cmd/grpc2http/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | 10 | "github.com/SberMarket-Tech/grpc-wiremock/internal/usecases/grpc2http" 11 | ) 12 | 13 | func main() { 14 | command, err := buildGenerateCommand() 15 | if err != nil { 16 | log.Fatalln("create cli command:", err.Error()) 17 | } 18 | if err = command.Execute(); err != nil { 19 | log.Fatalln("execute cli command:", err.Error()) 20 | } 21 | } 22 | 23 | type flags struct { 24 | inputPath string 25 | outputPath string 26 | baseURL string 27 | } 28 | 29 | func buildGenerateCommand() (*cobra.Command, error) { 30 | var ( 31 | args flags 32 | rootCmd = &cobra.Command{ 33 | RunE: func(cmd *cobra.Command, _ []string) error { 34 | gen := grpc2http.NewProxyGen(args.inputPath, args.outputPath, args.baseURL, os.Stdout) 35 | return gen.Generate(cmd.Context()) 36 | }, 37 | } 38 | ) 39 | 40 | rootCmd.Flags().StringVar(&args.inputPath, "input", "", "Directory with contract files") 41 | rootCmd.Flags().StringVarP(&args.outputPath, "output", "o", "generated_proxy", "Directory for generated proxy") 42 | rootCmd.Flags().StringVar(&args.baseURL, "base-url", "http://localhost:8080", "Proxy URL") 43 | 44 | if err := rootCmd.MarkFlagRequired("input"); err != nil { 45 | return nil, fmt.Errorf("make input flag persistent: %w", err) 46 | } 47 | 48 | return rootCmd, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/renderer/resolver.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/strutils" 7 | ) 8 | 9 | func resolveCollisions(value interface{}) { 10 | resolve(reflect.ValueOf(value)) 11 | } 12 | 13 | func changeSlice(rv reflect.Value) { 14 | if !rv.CanAddr() { 15 | return 16 | } 17 | 18 | for i := 0; i < rv.Len(); i++ { 19 | forEachSliceElem(rv.Index(i)) 20 | } 21 | } 22 | 23 | func resolve(rv reflect.Value) { 24 | if rv.Kind() == reflect.Ptr { 25 | rv = rv.Elem() 26 | } 27 | 28 | switch rv.Kind() { 29 | case reflect.Struct: 30 | changeStruct(rv) 31 | case reflect.Slice: 32 | changeSlice(rv) 33 | } 34 | } 35 | 36 | func changeStruct(rv reflect.Value) { 37 | if !rv.CanAddr() { 38 | return 39 | } 40 | 41 | for i := 0; i < rv.NumField(); i++ { 42 | field := rv.Field(i) 43 | forEachField(field) 44 | } 45 | } 46 | 47 | func forEachField(field reflect.Value) { 48 | switch field.Kind() { 49 | case reflect.String: 50 | newValue := strutils.ResolveNameIfCollides(field.String()) 51 | field.SetString(newValue) 52 | case reflect.Slice: 53 | changeSlice(field) 54 | } 55 | } 56 | 57 | func forEachSliceElem(item reflect.Value) { 58 | switch item.Kind() { 59 | case reflect.Struct: 60 | resolve(item) 61 | case reflect.String: 62 | newValue := strutils.ResolveNameIfCollides(item.String()) 63 | item.SetString(newValue) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /pkg/models/basecontract/loader/openapi/openapi.go: -------------------------------------------------------------------------------- 1 | package openapi 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/afero" 7 | 8 | contract "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/basecontract" 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/basecontract/parser/openapi" 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/sourcer/types" 11 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/sliceutils" 12 | ) 13 | 14 | type contractSourcer interface { 15 | SourceInputs() []string 16 | SourceLoadType() types.SourceLoadType 17 | SourceFileType() types.SourceFileType 18 | } 19 | 20 | type loader struct { 21 | fs afero.Fs 22 | source contractSourcer 23 | } 24 | 25 | func NewLoader(fs afero.Fs, source contractSourcer) loader { 26 | return loader{fs: fs, source: source} 27 | } 28 | 29 | func (l loader) LoadFile() (contract.SetOfContracts, error) { 30 | oaFiles := l.source.SourceInputs() 31 | 32 | contracts, err := openapi.ParseOpenAPIFiles(oaFiles) 33 | if err != nil { 34 | return nil, fmt.Errorf("parse openapi file: %w", err) 35 | } 36 | 37 | return contracts, nil 38 | } 39 | 40 | func (l loader) LoadDir() (contract.SetOfContracts, error) { 41 | oaFiles := l.source.SourceInputs() 42 | 43 | contracts, err := openapi.ParseOpenAPIDir(l.fs, sliceutils.FirstOf(oaFiles)) 44 | if err != nil { 45 | return nil, fmt.Errorf("parse openapi dir: %w", err) 46 | } 47 | 48 | return contracts, nil 49 | } 50 | -------------------------------------------------------------------------------- /pkg/generators/configs/supervisord/configure.go: -------------------------------------------------------------------------------- 1 | package supervisord 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | 7 | "github.com/spf13/afero" 8 | 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/generators/configs" 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/fsutils" 11 | ) 12 | 13 | // renderer abstracts how exactly project should be rendered. 14 | type renderer interface { 15 | Substitute(string, interface{}) (string, error) 16 | } 17 | 18 | type supervisordDomainConfView struct { 19 | Domain string 20 | Port string 21 | Root string 22 | } 23 | 24 | type Configuer struct { 25 | Renderer renderer 26 | OutputPath string 27 | 28 | afero.Fs 29 | } 30 | 31 | func (c Configuer) GenerateConfig(values configs.Values) error { 32 | const templatePath = "supervisord/files/supervisord.conf.tpl" 33 | 34 | confView := supervisordDomainConfView{Domain: values.Domain, Port: values.Port, Root: values.Root} 35 | 36 | content, err := c.Renderer.Substitute(templatePath, &confView) 37 | if err != nil { 38 | return fmt.Errorf("substitute: %w", err) 39 | } 40 | 41 | pathToSave := c.createConfigPath(values.Domain) 42 | 43 | if err = fsutils.WriteFile(c.Fs, pathToSave, content); err != nil { 44 | return fmt.Errorf("write: %w", err) 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func (c Configuer) createConfigPath(domain string) string { 51 | return filepath.Join(c.OutputPath, fmt.Sprintf("mock-%s.conf", domain)) 52 | } 53 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_1/report_8000.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8000 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.114 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 879.02 [#/sec] (mean) 22 | Time per request: 11.376 [ms] (mean) 23 | Time per request: 1.138 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 127.05 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.1 0 1 29 | Processing: 1 11 7.3 9 31 30 | Waiting: 1 10 7.2 8 31 31 | Total: 1 11 7.4 9 31 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 9 35 | 66% 13 36 | 75% 15 37 | 80% 18 38 | 90% 23 39 | 95% 25 40 | 98% 27 41 | 99% 31 42 | 100% 31 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8000.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8000 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.796 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 125.66 [#/sec] (mean) 22 | Time per request: 79.582 [ms] (mean) 23 | Time per request: 7.958 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 18.16 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.3 0 3 29 | Processing: 2 77 99.2 33 415 30 | Waiting: 2 75 98.5 30 415 31 | Total: 2 77 99.2 33 415 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 33 35 | 66% 53 36 | 75% 108 37 | 80% 152 38 | 90% 242 39 | 95% 337 40 | 98% 371 41 | 99% 415 42 | 100% 415 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8001.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8001 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.790 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 126.58 [#/sec] (mean) 22 | Time per request: 79.000 [ms] (mean) 23 | Time per request: 7.900 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 18.30 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 1 1.6 0 7 29 | Processing: 4 76 76.3 42 316 30 | Waiting: 4 73 75.7 37 316 31 | Total: 6 77 76.2 43 317 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 43 35 | 66% 90 36 | 75% 113 37 | 80% 131 38 | 90% 199 39 | 95% 227 40 | 98% 297 41 | 99% 317 42 | 100% 317 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8002.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8002 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.808 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 123.69 [#/sec] (mean) 22 | Time per request: 80.847 [ms] (mean) 23 | Time per request: 8.085 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 17.88 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.1 0 1 29 | Processing: 4 77 73.5 56 335 30 | Waiting: 3 75 74.2 51 335 31 | Total: 4 77 73.5 56 336 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 56 35 | 66% 74 36 | 75% 106 37 | 80% 117 38 | 90% 195 39 | 95% 278 40 | 98% 319 41 | 99% 336 42 | 100% 336 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8003.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8003 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.827 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 120.87 [#/sec] (mean) 22 | Time per request: 82.730 [ms] (mean) 23 | Time per request: 8.273 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 17.47 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.1 0 1 29 | Processing: 2 79 74.2 64 244 30 | Waiting: 2 74 69.9 60 243 31 | Total: 2 79 74.2 64 244 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 64 35 | 66% 105 36 | 75% 137 37 | 80% 160 38 | 90% 196 39 | 95% 230 40 | 98% 241 41 | 99% 244 42 | 100% 244 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8004.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8004 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.727 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 137.61 [#/sec] (mean) 22 | Time per request: 72.667 [ms] (mean) 23 | Time per request: 7.267 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 19.89 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.1 0 1 29 | Processing: 2 64 49.9 65 300 30 | Waiting: 2 63 49.9 65 299 31 | Total: 2 64 49.9 65 300 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 65 35 | 66% 80 36 | 75% 89 37 | 80% 114 38 | 90% 130 39 | 95% 140 40 | 98% 166 41 | 99% 300 42 | 100% 300 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8005.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8005 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.807 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 123.97 [#/sec] (mean) 22 | Time per request: 80.663 [ms] (mean) 23 | Time per request: 8.066 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 17.92 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.1 0 1 29 | Processing: 2 78 63.7 64 235 30 | Waiting: 2 75 62.5 64 229 31 | Total: 2 78 63.7 64 235 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 64 35 | 66% 119 36 | 75% 129 37 | 80% 138 38 | 90% 168 39 | 95% 183 40 | 98% 220 41 | 99% 235 42 | 100% 235 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8006.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8006 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.757 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 132.16 [#/sec] (mean) 22 | Time per request: 75.667 [ms] (mean) 23 | Time per request: 7.567 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 19.10 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.2 0 1 29 | Processing: 2 72 55.5 69 209 30 | Waiting: 2 70 54.3 65 208 31 | Total: 2 73 55.5 69 209 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 69 35 | 66% 87 36 | 75% 100 37 | 80% 116 38 | 90% 159 39 | 95% 194 40 | 98% 205 41 | 99% 209 42 | 100% 209 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8007.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8007 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.732 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 136.66 [#/sec] (mean) 22 | Time per request: 73.174 [ms] (mean) 23 | Time per request: 7.317 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 19.75 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.2 0 1 29 | Processing: 3 66 50.7 54 304 30 | Waiting: 2 61 45.1 51 220 31 | Total: 3 66 50.7 55 304 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 55 35 | 66% 84 36 | 75% 89 37 | 80% 98 38 | 90% 132 39 | 95% 159 40 | 98% 220 41 | 99% 304 42 | 100% 304 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8008.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8008 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.677 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 147.62 [#/sec] (mean) 22 | Time per request: 67.741 [ms] (mean) 23 | Time per request: 6.774 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 21.34 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.1 0 1 29 | Processing: 2 66 58.0 47 233 30 | Waiting: 2 63 57.5 43 233 31 | Total: 2 66 57.9 47 233 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 47 35 | 66% 71 36 | 75% 112 37 | 80% 133 38 | 90% 150 39 | 95% 181 40 | 98% 218 41 | 99% 233 42 | 100% 233 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_10/report_8009.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8009 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.604 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 165.69 [#/sec] (mean) 22 | Time per request: 60.354 [ms] (mean) 23 | Time per request: 6.035 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 23.95 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.1 0 1 29 | Processing: 3 54 38.0 52 160 30 | Waiting: 3 53 38.0 50 160 31 | Total: 3 54 38.0 52 161 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 52 35 | 66% 74 36 | 75% 86 37 | 80% 89 38 | 90% 105 39 | 95% 123 40 | 98% 143 41 | 99% 161 42 | 100% 161 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_2/report_8000.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8000 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.194 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 514.88 [#/sec] (mean) 22 | Time per request: 19.422 [ms] (mean) 23 | Time per request: 1.942 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 74.42 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.2 0 1 29 | Processing: 3 18 11.8 16 65 30 | Waiting: 3 17 11.2 14 47 31 | Total: 3 18 11.9 16 65 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 16 35 | 66% 23 36 | 75% 26 37 | 80% 29 38 | 90% 34 39 | 95% 43 40 | 98% 47 41 | 99% 65 42 | 100% 65 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_2/report_8001.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8001 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.175 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 571.37 [#/sec] (mean) 22 | Time per request: 17.502 [ms] (mean) 23 | Time per request: 1.750 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 82.58 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.1 0 1 29 | Processing: 2 16 12.5 13 59 30 | Waiting: 2 15 12.4 11 58 31 | Total: 2 16 12.6 13 59 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 13 35 | 66% 19 36 | 75% 22 37 | 80% 25 38 | 90% 37 39 | 95% 42 40 | 98% 58 41 | 99% 59 42 | 100% 59 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8000.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8000 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.527 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 65.47 [#/sec] (mean) 22 | Time per request: 152.749 [ms] (mean) 23 | Time per request: 15.275 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 9.46 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.9 0 9 29 | Processing: 3 139 140.1 100 638 30 | Waiting: 3 134 139.9 98 637 31 | Total: 4 139 140.1 100 638 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 100 35 | 66% 160 36 | 75% 184 37 | 80% 250 38 | 90% 401 39 | 95% 447 40 | 98% 498 41 | 99% 638 42 | 100% 638 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8001.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8001 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.741 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 57.44 [#/sec] (mean) 22 | Time per request: 174.108 [ms] (mean) 23 | Time per request: 17.411 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 8.30 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.4 0 3 29 | Processing: 2 162 176.7 87 698 30 | Waiting: 2 156 172.7 87 698 31 | Total: 3 162 176.8 87 698 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 87 35 | 66% 167 36 | 75% 296 37 | 80% 351 38 | 90% 446 39 | 95% 549 40 | 98% 648 41 | 99% 698 42 | 100% 698 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8002.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8002 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.652 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 60.54 [#/sec] (mean) 22 | Time per request: 165.175 [ms] (mean) 23 | Time per request: 16.517 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 8.75 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.4 0 3 29 | Processing: 2 162 189.6 72 674 30 | Waiting: 2 148 176.8 55 646 31 | Total: 3 163 189.6 72 674 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 72 35 | 66% 178 36 | 75% 219 37 | 80% 329 38 | 90% 557 39 | 95% 602 40 | 98% 654 41 | 99% 674 42 | 100% 674 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8003.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8003 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.512 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 66.15 [#/sec] (mean) 22 | Time per request: 151.164 [ms] (mean) 23 | Time per request: 15.116 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 9.56 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.9 0 9 29 | Processing: 3 132 106.1 144 375 30 | Waiting: 3 127 107.0 120 375 31 | Total: 3 133 106.1 144 375 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 144 35 | 66% 167 36 | 75% 193 37 | 80% 256 38 | 90% 291 39 | 95% 322 40 | 98% 338 41 | 99% 375 42 | 100% 375 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8004.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8004 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.222 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 81.82 [#/sec] (mean) 22 | Time per request: 122.219 [ms] (mean) 23 | Time per request: 12.222 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 11.83 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.9 0 9 29 | Processing: 3 109 129.1 72 594 30 | Waiting: 3 103 126.3 67 594 31 | Total: 3 109 129.1 72 594 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 72 35 | 66% 106 36 | 75% 146 37 | 80% 179 38 | 90% 356 39 | 95% 425 40 | 98% 459 41 | 99% 594 42 | 100% 594 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8005.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8005 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.855 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 53.90 [#/sec] (mean) 22 | Time per request: 185.522 [ms] (mean) 23 | Time per request: 18.552 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 7.79 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.8 0 4 29 | Processing: 2 181 289.7 39 1149 30 | Waiting: 2 180 289.4 39 1149 31 | Total: 2 181 289.7 39 1149 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 39 35 | 66% 158 36 | 75% 212 37 | 80% 241 38 | 90% 886 39 | 95% 1003 40 | 98% 1046 41 | 99% 1149 42 | 100% 1149 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8006.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8006 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.835 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 119.81 [#/sec] (mean) 22 | Time per request: 83.466 [ms] (mean) 23 | Time per request: 8.347 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 17.32 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.4 0 2 29 | Processing: 4 75 68.5 60 388 30 | Waiting: 4 71 68.4 56 365 31 | Total: 4 75 68.4 60 388 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 60 35 | 66% 74 36 | 75% 112 37 | 80% 118 38 | 90% 167 39 | 95% 219 40 | 98% 280 41 | 99% 388 42 | 100% 388 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8007.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8007 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.464 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 68.29 [#/sec] (mean) 22 | Time per request: 146.436 [ms] (mean) 23 | Time per request: 14.644 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 9.87 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.9 0 5 29 | Processing: 2 115 145.2 46 595 30 | Waiting: 2 107 137.1 40 572 31 | Total: 2 115 145.1 48 595 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 48 35 | 66% 103 36 | 75% 175 37 | 80% 188 38 | 90% 325 39 | 95% 514 40 | 98% 572 41 | 99% 595 42 | 100% 595 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8008.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8008 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.779 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 56.21 [#/sec] (mean) 22 | Time per request: 177.905 [ms] (mean) 23 | Time per request: 17.791 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 8.12 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 1.1 0 4 29 | Processing: 4 171 202.9 63 787 30 | Waiting: 2 158 191.6 56 787 31 | Total: 4 172 202.9 63 791 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 63 35 | 66% 227 36 | 75% 276 37 | 80% 373 38 | 90% 488 39 | 95% 662 40 | 98% 770 41 | 99% 791 42 | 100% 791 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8009.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8009 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.563 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 63.99 [#/sec] (mean) 22 | Time per request: 156.283 [ms] (mean) 23 | Time per request: 15.628 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 9.25 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.4 0 2 29 | Processing: 3 145 149.2 98 666 30 | Waiting: 3 140 146.2 98 665 31 | Total: 4 145 149.3 98 667 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 98 35 | 66% 152 36 | 75% 227 37 | 80% 268 38 | 90% 399 39 | 95% 458 40 | 98% 576 41 | 99% 667 42 | 100% 667 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8010.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8010 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.316 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 76.01 [#/sec] (mean) 22 | Time per request: 131.567 [ms] (mean) 23 | Time per request: 13.157 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 10.99 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.3 0 2 29 | Processing: 2 112 103.4 89 354 30 | Waiting: 2 110 103.4 87 353 31 | Total: 2 112 103.5 90 355 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 90 35 | 66% 115 36 | 75% 154 37 | 80% 192 38 | 90% 325 39 | 95% 339 40 | 98% 355 41 | 99% 355 42 | 100% 355 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8011.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8011 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.740 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 57.47 [#/sec] (mean) 22 | Time per request: 174.012 [ms] (mean) 23 | Time per request: 17.401 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 8.31 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 1 2.5 0 9 29 | Processing: 2 161 162.0 114 649 30 | Waiting: 2 155 156.6 109 648 31 | Total: 2 162 163.2 115 658 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 115 35 | 66% 166 36 | 75% 297 37 | 80% 316 38 | 90% 387 39 | 95% 503 40 | 98% 653 41 | 99% 658 42 | 100% 658 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8012.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8012 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.730 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 57.80 [#/sec] (mean) 22 | Time per request: 173.012 [ms] (mean) 23 | Time per request: 17.301 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 8.35 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.3 0 2 29 | Processing: 2 168 253.2 55 1046 30 | Waiting: 2 164 251.7 50 1045 31 | Total: 2 168 253.3 56 1046 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 56 35 | 66% 139 36 | 75% 155 37 | 80% 253 38 | 90% 631 39 | 95% 856 40 | 98% 945 41 | 99% 1046 42 | 100% 1046 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8013.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8013 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.641 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 60.94 [#/sec] (mean) 22 | Time per request: 164.097 [ms] (mean) 23 | Time per request: 16.410 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 8.81 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.3 0 2 29 | Processing: 3 156 195.5 65 816 30 | Waiting: 3 149 191.5 61 816 31 | Total: 3 157 195.6 65 816 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 65 35 | 66% 174 36 | 75% 254 37 | 80% 283 38 | 90% 392 39 | 95% 754 40 | 98% 774 41 | 99% 816 42 | 100% 816 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8014.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8014 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.474 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 67.86 [#/sec] (mean) 22 | Time per request: 147.352 [ms] (mean) 23 | Time per request: 14.735 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 9.81 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.6 0 5 29 | Processing: 2 141 152.0 104 635 30 | Waiting: 2 136 151.6 98 635 31 | Total: 2 141 152.0 104 635 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 104 35 | 66% 130 36 | 75% 185 37 | 80% 222 38 | 90% 384 39 | 95% 535 40 | 98% 628 41 | 99% 635 42 | 100% 635 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8015.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8015 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.835 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 54.50 [#/sec] (mean) 22 | Time per request: 183.484 [ms] (mean) 23 | Time per request: 18.348 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 7.88 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.2 0 1 29 | Processing: 3 179 189.1 108 731 30 | Waiting: 3 177 189.2 108 731 31 | Total: 3 179 189.2 108 731 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 108 35 | 66% 215 36 | 75% 334 37 | 80% 349 38 | 90% 474 39 | 95% 580 40 | 98% 625 41 | 99% 731 42 | 100% 731 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8016.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8016 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.699 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 58.87 [#/sec] (mean) 22 | Time per request: 169.875 [ms] (mean) 23 | Time per request: 16.987 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 8.51 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.3 0 1 29 | Processing: 2 164 177.5 124 755 30 | Waiting: 2 161 178.3 121 754 31 | Total: 3 164 177.6 124 755 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 124 35 | 66% 209 36 | 75% 259 37 | 80% 274 38 | 90% 423 39 | 95% 550 40 | 98% 693 41 | 99% 755 42 | 100% 755 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8017.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8017 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.807 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 55.35 [#/sec] (mean) 22 | Time per request: 180.655 [ms] (mean) 23 | Time per request: 18.065 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 8.00 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.3 0 2 29 | Processing: 3 176 264.8 58 1111 30 | Waiting: 2 174 264.6 54 1111 31 | Total: 3 176 264.8 58 1111 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 58 35 | 66% 115 36 | 75% 266 37 | 80% 340 38 | 90% 432 39 | 95% 815 40 | 98% 1110 41 | 99% 1111 42 | 100% 1111 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8018.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8018 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.724 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 58.01 [#/sec] (mean) 22 | Time per request: 172.397 [ms] (mean) 23 | Time per request: 17.240 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 8.38 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.2 0 1 29 | Processing: 3 165 188.4 81 686 30 | Waiting: 2 160 187.9 76 685 31 | Total: 3 165 188.5 81 686 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 81 35 | 66% 146 36 | 75% 175 37 | 80% 256 38 | 90% 539 39 | 95% 632 40 | 98% 635 41 | 99% 686 42 | 100% 686 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_20/report_8019.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8019 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 1.179 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 84.78 [#/sec] (mean) 22 | Time per request: 117.950 [ms] (mean) 23 | Time per request: 11.795 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 12.25 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.3 0 1 29 | Processing: 3 108 91.4 92 405 30 | Waiting: 3 106 91.6 91 405 31 | Total: 4 109 91.4 92 405 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 92 35 | 66% 137 36 | 75% 168 37 | 80% 200 38 | 90% 258 39 | 95% 275 40 | 98% 280 41 | 99% 405 42 | 100% 405 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_5/report_8000.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8000 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.440 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 227.08 [#/sec] (mean) 22 | Time per request: 44.037 [ms] (mean) 23 | Time per request: 4.404 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 32.82 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.6 0 3 29 | Processing: 3 42 34.9 32 146 30 | Waiting: 2 39 34.6 28 146 31 | Total: 3 42 35.0 32 146 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 32 35 | 66% 52 36 | 75% 70 37 | 80% 76 38 | 90% 87 39 | 95% 115 40 | 98% 127 41 | 99% 146 42 | 100% 146 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_5/report_8001.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8001 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.368 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 271.70 [#/sec] (mean) 22 | Time per request: 36.805 [ms] (mean) 23 | Time per request: 3.681 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 39.27 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.2 0 1 29 | Processing: 2 35 28.6 26 122 30 | Waiting: 2 34 28.0 26 120 31 | Total: 2 35 28.6 27 122 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 27 35 | 66% 42 36 | 75% 47 37 | 80% 59 38 | 90% 84 39 | 95% 96 40 | 98% 110 41 | 99% 122 42 | 100% 122 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_5/report_8002.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8002 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.442 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 226.12 [#/sec] (mean) 22 | Time per request: 44.223 [ms] (mean) 23 | Time per request: 4.422 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 32.68 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.4 0 2 29 | Processing: 2 42 36.3 33 161 30 | Waiting: 2 40 35.4 31 160 31 | Total: 2 42 36.4 33 161 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 33 35 | 66% 47 36 | 75% 66 37 | 80% 71 38 | 90% 97 39 | 95% 115 40 | 98% 142 41 | 99% 161 42 | 100% 161 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_5/report_8003.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8003 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.456 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 219.07 [#/sec] (mean) 22 | Time per request: 45.647 [ms] (mean) 23 | Time per request: 4.565 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 31.66 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.2 0 1 29 | Processing: 3 44 34.9 40 140 30 | Waiting: 3 43 34.8 38 139 31 | Total: 3 44 35.0 40 140 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 40 35 | 66% 57 36 | 75% 69 37 | 80% 75 38 | 90% 96 39 | 95% 111 40 | 98% 133 41 | 99% 140 42 | 100% 140 (longest request) 43 | -------------------------------------------------------------------------------- /benchmarks/output/api_count_5/report_8004.txt: -------------------------------------------------------------------------------- 1 | This is ApacheBench, Version 2.3 <$Revision: 1903618 $> 2 | Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 3 | Licensed to The Apache Software Foundation, http://www.apache.org/ 4 | 5 | Benchmarking wiremock (be patient).....done 6 | 7 | 8 | Server Software: 9 | Server Hostname: wiremock 10 | Server Port: 8004 11 | 12 | Document Path: /HealthCheck 13 | Document Length: 7 bytes 14 | 15 | Concurrency Level: 10 16 | Time taken for tests: 0.432 seconds 17 | Complete requests: 100 18 | Failed requests: 0 19 | Total transferred: 14800 bytes 20 | HTML transferred: 700 bytes 21 | Requests per second: 231.49 [#/sec] (mean) 22 | Time per request: 43.198 [ms] (mean) 23 | Time per request: 4.320 [ms] (mean, across all concurrent requests) 24 | Transfer rate: 33.46 [Kbytes/sec] received 25 | 26 | Connection Times (ms) 27 | min mean[+/-sd] median max 28 | Connect: 0 0 0.2 0 1 29 | Processing: 4 41 26.8 43 126 30 | Waiting: 3 39 24.4 40 110 31 | Total: 4 41 26.8 43 126 32 | 33 | Percentage of the requests served within a certain time (ms) 34 | 50% 43 35 | 66% 48 36 | 75% 59 37 | 80% 62 38 | 90% 76 39 | 95% 94 40 | 98% 111 41 | 99% 126 42 | 100% 126 (longest request) 43 | -------------------------------------------------------------------------------- /pkg/generators/configs/generate.go: -------------------------------------------------------------------------------- 1 | package configs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/spf13/afero" 8 | 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/wiremock/config" 10 | ) 11 | 12 | type Configuer interface { 13 | GenerateConfig(values Values) error 14 | } 15 | 16 | type Reloader interface { 17 | ReloadConfig(ctx context.Context) error 18 | } 19 | 20 | type Values struct { 21 | Domain string 22 | Port string 23 | Root string 24 | } 25 | 26 | type runner struct { 27 | fs afero.Fs 28 | config config.Wiremock 29 | } 30 | 31 | func NewRunner(fs afero.Fs, config config.Wiremock) *runner { 32 | return &runner{fs: fs, config: config} 33 | } 34 | 35 | func (g *runner) RunConfiguers(configuers ...Configuer) error { 36 | for _, service := range g.config.Services { 37 | values := Values{ 38 | Domain: service.Name, 39 | Root: service.RootDir, 40 | Port: fmt.Sprint(service.Port), 41 | } 42 | 43 | for _, configuer := range configuers { 44 | if err := configuer.GenerateConfig(values); err != nil { 45 | return fmt.Errorf("generate config: %w", err) 46 | } 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func (g *runner) RunReloaders(ctx context.Context, reloaders ...Reloader) error { 54 | for _, reloader := range reloaders { 55 | if err := reloader.ReloadConfig(ctx); err != nil { 56 | return fmt.Errorf("reload config: %w", err) 57 | } 58 | } 59 | 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/generators/certificates/test/tester.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/http/httptest" 9 | "strings" 10 | ) 11 | 12 | const successMessage = "success" 13 | 14 | type CertsTester struct { 15 | Client *http.Client 16 | Server *httptest.Server 17 | } 18 | 19 | func NewCertsTester(serverCert, clientCert *tls.Config) CertsTester { 20 | server := httptest.NewUnstartedServer(createTestHandler()) 21 | server.TLS = serverCert 22 | 23 | client := &http.Client{ 24 | Transport: &http.Transport{ 25 | TLSClientConfig: clientCert, 26 | }, 27 | } 28 | 29 | return CertsTester{Server: server, Client: client} 30 | } 31 | 32 | func (t *CertsTester) DoTestRequest() error { 33 | response, err := t.Client.Get(t.Server.URL) 34 | if err != nil { 35 | return fmt.Errorf("do get: %w", err) 36 | } 37 | 38 | responseContent, err := io.ReadAll(response.Body) 39 | if err != nil { 40 | return fmt.Errorf("read body: %w", err) 41 | } 42 | 43 | return requireEqualBody(string(responseContent), successMessage) 44 | } 45 | 46 | func createTestHandler() http.HandlerFunc { 47 | return func(writer http.ResponseWriter, request *http.Request) { 48 | fmt.Fprintln(writer, successMessage) 49 | } 50 | } 51 | 52 | func requireEqualBody(got, target string) error { 53 | if strings.TrimSpace(got) != target { 54 | return fmt.Errorf("not equal got: %s, target: %s", got, target) 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /internal/usecases/confgen/mocks/generate_mock.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.23.1. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | context "context" 7 | 8 | mock "github.com/stretchr/testify/mock" 9 | ) 10 | 11 | // CommandRunner is an autogenerated mock type for the commandRunner type 12 | type CommandRunner struct { 13 | mock.Mock 14 | } 15 | 16 | // Run provides a mock function with given fields: ctx, cmd, args 17 | func (_m *CommandRunner) Run(ctx context.Context, cmd string, args ...string) error { 18 | _va := make([]interface{}, len(args)) 19 | for _i := range args { 20 | _va[_i] = args[_i] 21 | } 22 | var _ca []interface{} 23 | _ca = append(_ca, ctx, cmd) 24 | _ca = append(_ca, _va...) 25 | ret := _m.Called(_ca...) 26 | 27 | var r0 error 28 | if rf, ok := ret.Get(0).(func(context.Context, string, ...string) error); ok { 29 | r0 = rf(ctx, cmd, args...) 30 | } else { 31 | r0 = ret.Error(0) 32 | } 33 | 34 | return r0 35 | } 36 | 37 | type mockConstructorTestingTNewCommandRunner interface { 38 | mock.TestingT 39 | Cleanup(func()) 40 | } 41 | 42 | // NewCommandRunner creates a new instance of CommandRunner. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. 43 | func NewCommandRunner(t mockConstructorTestingTNewCommandRunner) *CommandRunner { 44 | mock := &CommandRunner{} 45 | mock.Mock.Test(t) 46 | 47 | t.Cleanup(func() { mock.AssertExpectations(t) }) 48 | 49 | return mock 50 | } 51 | -------------------------------------------------------------------------------- /docs/benchmarks.md: -------------------------------------------------------------------------------- 1 | ## Benchmarks 2 | 3 | Suppose you need to deploy a lot of mock APIs for your service. 4 | 5 | Let's determine how many APIs you can deploy without any noticeable performance degradation. 6 | 7 | I tested on a MacBook Pro 16" with these characteristics: 8 | - 2.6 GHz 6-Core Intel Core i7; 9 | - 16 GB 2667 MHz DDR4; 10 | - Intel UHD Graphics 630 1536 MB. 11 | 12 | For testing we will choose a simple mock, which we will request from each mock API. We will send requests using the ab utility. 13 | An example of such a mock: 14 | 15 | ```json 16 | { 17 | "request" : { 18 | "urlPath" : "/HealthCheck", 19 | "method" : "GET" 20 | }, 21 | "response" : { 22 | "status" : 200, 23 | "body" : "success", 24 | "headers" : { 25 | "Content-Type" : "application/json" 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | Command to run tests: 32 | ```bash 33 | ab -c 10 -n 100 "http://wiremock:${PORT}/HealthCheck" 34 | ``` 35 | 36 | Test parameters: 37 | - the number of requests is 100; 38 | - 10 requests can be executed simultaneously. 39 | 40 | Test Scenario: 41 | - preparing N Wiremock Standalone processes; 42 | - waiting for all processes to be ready to serve clients; 43 | - running the ```ab``` utility for all processes simultaneously. 44 | 45 | ### Results: 46 | 47 | In the graph you can see that with the addition of a Wiremock instance, the maximum response time doubles. 48 | 49 | ![comparison](images/comparison.png) 50 | 51 | -------------------------------------------------------------------------------- /pkg/wiremock/configsync/sync.go: -------------------------------------------------------------------------------- 1 | package configsync 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | 7 | "github.com/spf13/afero" 8 | 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/fsutils" 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/wiremock/config" 11 | ) 12 | 13 | func SyncWiremockConfig(fs afero.Fs, wiremockConfig config.Wiremock, wiremockPath string) (config.Wiremock, error) { 14 | domainDirectories, err := fsutils.GatherDirs(fs, wiremockPath) 15 | if err != nil { 16 | return config.Wiremock{}, fmt.Errorf("gather domain directories: %w", err) 17 | } 18 | 19 | nextAvailablePort := config.GatherPorts(wiremockConfig).Allocate() 20 | 21 | domainToService := map[string]config.Service{} 22 | targetDomainToService := map[string]config.Service{} 23 | 24 | for _, service := range wiremockConfig.Services { 25 | domainToService[service.Name] = service 26 | } 27 | 28 | for _, domainDir := range domainDirectories { 29 | domain := filepath.Base(domainDir.Name()) 30 | 31 | service, exists := domainToService[domain] 32 | if exists { 33 | targetDomainToService[domain] = service 34 | } else { 35 | targetDomainToService[domain] = config.NewService(wiremockPath, domain, nextAvailablePort) 36 | nextAvailablePort++ 37 | } 38 | } 39 | 40 | var targetWiremockConfig config.Wiremock 41 | for _, service := range targetDomainToService { 42 | targetWiremockConfig.Services = append(targetWiremockConfig.Services, service) 43 | } 44 | 45 | return targetWiremockConfig, nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/models/basecontract/unifier/openapi/openapi.go: -------------------------------------------------------------------------------- 1 | package openapi 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "path/filepath" 7 | 8 | "github.com/spf13/afero" 9 | "gopkg.in/yaml.v3" 10 | 11 | contract "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/basecontract" 12 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/fsutils" 13 | ) 14 | 15 | type openapiUnifier struct { 16 | fs afero.Fs 17 | } 18 | 19 | func NewUnifier(fs afero.Fs) *openapiUnifier { 20 | return &openapiUnifier{fs: fs} 21 | } 22 | 23 | func (o *openapiUnifier) Unify(ctx context.Context, contracts contract.SetOfContracts, path string) error { 24 | for _, contractToUnify := range contracts { 25 | if err := o.saveFromDescriptor(contractToUnify, path); err != nil { 26 | return fmt.Errorf("save from descriptor: %w", err) 27 | } 28 | } 29 | 30 | return nil 31 | } 32 | 33 | func (o *openapiUnifier) saveFromDescriptor(contractToUnify contract.Contract, path string) error { 34 | descriptor, err := contract.OpenAPIFromAny(contractToUnify) 35 | if err != nil { 36 | return fmt.Errorf("get openapi descriptor: %w", err) 37 | } 38 | 39 | content, err := yaml.Marshal(descriptor) 40 | if err != nil { 41 | return fmt.Errorf("marshal contract %s: %w", contractToUnify.HeaderPath, err) 42 | } 43 | 44 | pathToSave := filepath.Join(path, filepath.Base(contractToUnify.HeaderPath)) 45 | 46 | if err = fsutils.WriteFile(o.fs, pathToSave, string(content)); err != nil { 47 | return fmt.Errorf("write unified contract %s: %w", contractToUnify.HeaderPath, err) 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/runner/runner_test.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "k8s.io/utils/exec" 9 | testingexec "k8s.io/utils/exec/testing" 10 | 11 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/executils" 12 | ) 13 | 14 | type execArgs struct { 15 | command string 16 | args []string 17 | output string 18 | err error 19 | } 20 | 21 | func Test_shell_Run(t *testing.T) { 22 | tests := []struct { 23 | name string 24 | 25 | execScript execArgs 26 | 27 | want string 28 | wantErr error 29 | }{ 30 | { 31 | execScript: execArgs{"protoc", []string{"-I", "/tmp/protos", "baugi.proto"}, "", exec.ErrExecutableNotFound}, 32 | wantErr: exec.ErrExecutableNotFound, 33 | }, 34 | } 35 | 36 | for _, tt := range tests { 37 | t.Run(tt.name, func(t *testing.T) { 38 | fakeExec := &testingexec.FakeExec{ExactOrder: true} 39 | 40 | fakeCmd := &testingexec.FakeCmd{} 41 | 42 | cmdAction := executils.MakeFakeCmd(fakeCmd, tt.execScript.command, tt.execScript.args...) 43 | outputAction := executils.MakeFakeOutput(tt.execScript.output, tt.execScript.err) 44 | 45 | fakeCmd.CombinedOutputScript = append(fakeCmd.CombinedOutputScript, outputAction) 46 | fakeExec.CommandScript = append(fakeExec.CommandScript, cmdAction) 47 | 48 | ctx := context.Background() 49 | err := New(fakeExec).Run(ctx, tt.execScript.command, tt.execScript.args...) 50 | 51 | if tt.wantErr == nil { 52 | assert.NoError(t, err) 53 | } else { 54 | assert.ErrorIs(t, err, tt.wantErr) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pkg/wiremock/client/model.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | type Request struct { 4 | URLPath string `json:"urlPath"` 5 | Method string `json:"method"` 6 | } 7 | 8 | type Accept struct { 9 | Contains string `json:"contains"` 10 | } 11 | 12 | type Response struct { 13 | Status int `json:"status"` 14 | Body string `json:"body"` 15 | Headers Headers `json:"headers"` 16 | } 17 | 18 | type Headers struct { 19 | ContentType string `json:"Content-Type"` 20 | } 21 | 22 | type Metadata struct { 23 | Description string `json:"description"` 24 | } 25 | 26 | type Mock struct { 27 | Name string `json:"name"` 28 | 29 | Request Request `json:"request"` 30 | Response Response `json:"response"` 31 | Metadata Metadata `json:"metadata"` 32 | } 33 | 34 | func DefaultMock() Mock { 35 | return Mock{ 36 | Request: Request{}, 37 | Response: Response{Headers: Headers{ContentType: "application/json"}}, 38 | } 39 | } 40 | 41 | func (m *Mock) WithName(name string) *Mock { 42 | m.Name = name 43 | return m 44 | } 45 | 46 | func (m *Mock) WithDescription(description string) *Mock { 47 | m.Metadata.Description = description 48 | return m 49 | } 50 | 51 | func (m *Mock) WithRequestUrlPath(urlPath string) *Mock { 52 | m.Request.URLPath = urlPath 53 | return m 54 | } 55 | 56 | func (m *Mock) WithRequestMethod(method string) *Mock { 57 | m.Request.Method = method 58 | return m 59 | } 60 | 61 | func (m *Mock) WithResponseStatusCode(statusCode int) *Mock { 62 | m.Response.Status = statusCode 63 | return m 64 | } 65 | 66 | func (m *Mock) WithResponseBody(body string) *Mock { 67 | m.Response.Body = body 68 | return m 69 | } 70 | -------------------------------------------------------------------------------- /pkg/builder/update.go: -------------------------------------------------------------------------------- 1 | package builder 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jhump/protoreflect/desc" 7 | 8 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/blacklist" 9 | ) 10 | 11 | type protoUpdater interface { 12 | Name() string 13 | Update(contract *desc.FileDescriptor) (*desc.FileDescriptor, error) 14 | } 15 | 16 | func UpdateContracts(contracts []*desc.FileDescriptor, updaters ...protoUpdater) ([]*desc.FileDescriptor, error) { 17 | var updatedDescriptors []*desc.FileDescriptor 18 | 19 | for _, descriptor := range contracts { 20 | goPackage := protoGoPackage(descriptor) 21 | 22 | if blacklist.IsGoogleAPIContract(goPackage) { 23 | updatedDescriptors = append(updatedDescriptors, descriptor) 24 | continue 25 | } 26 | 27 | if blacklist.IsDeliveredWithProtoc(goPackage) { 28 | updatedDescriptors = append(updatedDescriptors, descriptor) 29 | continue 30 | } 31 | 32 | updatedDescriptor := descriptor 33 | 34 | var err error 35 | for _, updater := range updaters { 36 | updatedDescriptor, err = updater.Update(updatedDescriptor) 37 | if err != nil { 38 | return nil, fmt.Errorf("update proto descriptor with %s: %w", updater.Name(), err) 39 | } 40 | } 41 | 42 | updatedDescriptors = append(updatedDescriptors, updatedDescriptor) 43 | } 44 | 45 | return updatedDescriptors, nil 46 | } 47 | 48 | // TODO separate common functions 49 | func protoGoPackage(descriptor *desc.FileDescriptor) string { 50 | if descriptor.GetFileOptions() == nil { 51 | return "" 52 | } 53 | 54 | goPackage := descriptor.GetFileOptions().GoPackage 55 | 56 | if goPackage == nil { 57 | return "" 58 | } 59 | 60 | return *goPackage 61 | } 62 | -------------------------------------------------------------------------------- /pkg/generators/certificates/domains.go: -------------------------------------------------------------------------------- 1 | package certificates 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/strutils" 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/wiremock/config" 11 | ) 12 | 13 | var domains = []string{ 14 | "com", "net", "gov", "mil", "io", 15 | "ru", "org", "local", "tech", "dev", 16 | "online", "internal", "team", 17 | } 18 | 19 | var commonDomains = []string{ 20 | "localhost", "mock", 21 | "grpc-wiremock", 22 | } 23 | 24 | func (g *certsGen) collectDomains(commonDomains, domains []string) ([]string, error) { 25 | wiremockConfig, err := g.opener.Open() 26 | if err != nil { 27 | log.Printf("certgen: open wiremock config: %s", err) 28 | 29 | if err = handleOpenerErrors(err); err != nil { 30 | return nil, err 31 | } 32 | } 33 | 34 | var targetDomains []string 35 | 36 | for _, service := range wiremockConfig.Services { 37 | targetDomains = append(targetDomains, service.Name) 38 | } 39 | 40 | var certDomains []string 41 | 42 | for _, domain := range targetDomains { 43 | for _, ext := range domains { 44 | certDomains = append(certDomains, domain, fmt.Sprintf("%s.%s", domain, ext)) 45 | } 46 | } 47 | 48 | if len(certDomains) == 0 { 49 | return strutils.UniqueAndSorted(commonDomains...), nil 50 | } 51 | 52 | return strutils.UniqueAndSorted(append(certDomains, commonDomains...)...), nil 53 | } 54 | 55 | func handleOpenerErrors(err error) error { 56 | if errors.Is(err, os.ErrNotExist) || 57 | errors.Is(err, config.EmptyWiremockConfigErr) { 58 | return nil 59 | } 60 | 61 | return fmt.Errorf("read wiremock config: %w", err) 62 | } 63 | -------------------------------------------------------------------------------- /pkg/models/protocontract/traverser/traverse_test.go: -------------------------------------------------------------------------------- 1 | package traverser 2 | 3 | import ( 4 | "path/filepath" 5 | "testing" 6 | 7 | "github.com/spf13/afero" 8 | "github.com/stretchr/testify/assert" 9 | 10 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/protocontract/loader" 11 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/sourcer" 12 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/sourcer/types" 13 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/fsutils" 14 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/strutils" 15 | ) 16 | 17 | var projectDir = filepath.Join(fsutils.CurrentDir(), "../../../..") 18 | 19 | func TestSetOfContracts_Descriptors(t *testing.T) { 20 | tests := []struct { 21 | name string 22 | 23 | fs afero.Fs 24 | path string 25 | 26 | wantFiles []string 27 | }{ 28 | { 29 | fs: afero.NewOsFs(), 30 | path: filepath.Join(projectDir, "static/tests/data/static-examples/with-common-imports"), 31 | wantFiles: []string{ 32 | "example.proto", 33 | "google/protobuf/empty.proto", 34 | }, 35 | }, 36 | } 37 | for _, tt := range tests { 38 | t.Run(tt.name, func(t *testing.T) { 39 | sourcerInstance, err := sourcer.New(tt.fs, tt.path, types.ProtoType) 40 | assert.NoError(t, err) 41 | 42 | contracts, err := loader.Load(tt.fs, sourcerInstance) 43 | assert.NoError(t, err) 44 | 45 | descriptors := Descriptors(contracts) 46 | assert.NotNil(t, descriptors) 47 | 48 | var files []string 49 | for _, desc := range descriptors { 50 | files = append(files, desc.GetName()) 51 | } 52 | 53 | assert.ElementsMatch(t, tt.wantFiles, strutils.UniqueAndSorted(files...)) 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /static/tests/data/openapi/petstore-merged/petstore.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | components: 3 | schemas: 4 | Error: 5 | type: object 6 | required: 7 | - code 8 | - message 9 | properties: 10 | code: 11 | type: integer 12 | format: int32 13 | message: 14 | type: string 15 | Pet: 16 | type: object 17 | required: 18 | - id 19 | - name 20 | properties: 21 | id: 22 | type: integer 23 | format: int64 24 | name: 25 | type: string 26 | SchemasError: 27 | type: object 28 | required: 29 | - code 30 | - message 31 | properties: 32 | code: 33 | type: integer 34 | format: int32 35 | message: 36 | type: string 37 | SchemasPet: 38 | type: object 39 | required: 40 | - id 41 | - name 42 | properties: 43 | id: 44 | type: integer 45 | format: int64 46 | name: 47 | type: string 48 | info: 49 | title: Petstore 50 | version: 1.0.0 51 | paths: 52 | /pets: 53 | get: 54 | operationId: listPets 55 | responses: 56 | "200": 57 | description: pet response 58 | content: 59 | application/json: 60 | schema: 61 | type: array 62 | items: 63 | $ref: '#/components/schemas/Pet' 64 | default: 65 | description: unexpected error 66 | content: 67 | application/json: 68 | schema: 69 | $ref: '#/components/schemas/Error' 70 | -------------------------------------------------------------------------------- /static/tests/data/openapi/petstore-and-users-merged/petstore.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | components: 3 | schemas: 4 | Error: 5 | type: object 6 | required: 7 | - code 8 | - message 9 | properties: 10 | code: 11 | type: integer 12 | format: int32 13 | message: 14 | type: string 15 | Pet: 16 | type: object 17 | required: 18 | - id 19 | - name 20 | properties: 21 | id: 22 | type: integer 23 | format: int64 24 | name: 25 | type: string 26 | SchemasError: 27 | type: object 28 | required: 29 | - code 30 | - message 31 | properties: 32 | code: 33 | type: integer 34 | format: int32 35 | message: 36 | type: string 37 | SchemasPet: 38 | type: object 39 | required: 40 | - id 41 | - name 42 | properties: 43 | id: 44 | type: integer 45 | format: int64 46 | name: 47 | type: string 48 | info: 49 | title: Petstore 50 | version: 1.0.0 51 | paths: 52 | /pets: 53 | get: 54 | operationId: listPets 55 | responses: 56 | "200": 57 | description: pet response 58 | content: 59 | application/json: 60 | schema: 61 | type: array 62 | items: 63 | $ref: '#/components/schemas/Pet' 64 | default: 65 | description: unexpected error 66 | content: 67 | application/json: 68 | schema: 69 | $ref: '#/components/schemas/Error' 70 | -------------------------------------------------------------------------------- /static/proxy/template/layout/pkg/wiremock/client_test.go: -------------------------------------------------------------------------------- 1 | package wiremock 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "net/http" 7 | "testing" 8 | 9 | "google.golang.org/grpc/metadata" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func Test_enrichWithMetaData(t *testing.T) { 15 | tests := []struct { 16 | name string 17 | md metadata.MD 18 | 19 | wantHost string 20 | wantHeader http.Header 21 | }{ 22 | { 23 | name: "empty metadata means empty host and header", 24 | md: metadata.MD{}, 25 | wantHost: "", 26 | wantHeader: http.Header{}, 27 | }, 28 | { 29 | name: "authority metadata fills request host", 30 | md: metadata.MD{ 31 | ":authority": []string{ 32 | "push-sender.test", 33 | }, 34 | }, 35 | wantHost: "push-sender.test", 36 | wantHeader: http.Header{}, 37 | }, 38 | { 39 | name: "custom metadata fills request header", 40 | md: metadata.MD{ 41 | "custom": []string{"testHeader"}, 42 | "content-type": []string{ 43 | "application/grpc", 44 | }, 45 | }, 46 | wantHost: "", 47 | wantHeader: http.Header{ 48 | "Custom": []string{"testHeader"}, 49 | "Content-Type": []string{"application/grpc"}, 50 | }, 51 | }, 52 | } 53 | 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | request, err := DefaultRequest( 57 | metadata.NewIncomingContext(context.Background(), tt.md), 58 | "/Notify", bytes.NewReader([]byte{})) 59 | require.NoError(t, err) 60 | 61 | request = enrichWithMetaData(request) 62 | 63 | require.Equal(t, tt.wantHost, request.Host) 64 | require.Equal(t, tt.wantHeader, request.Header) 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/renderer/renderer.go: -------------------------------------------------------------------------------- 1 | package renderer 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/spf13/afero" 10 | ) 11 | 12 | type renderer struct { 13 | fs afero.Fs 14 | 15 | tpls map[string]*template.Template 16 | 17 | pathToTemplates string 18 | } 19 | 20 | func New(fs afero.Fs, path string) (renderer, error) { 21 | newRenderer := renderer{ 22 | fs: fs, 23 | pathToTemplates: path, 24 | 25 | tpls: map[string]*template.Template{}, 26 | } 27 | 28 | templatesGlob := filepath.Join(path, "*") 29 | 30 | files, err := afero.Glob(fs, templatesGlob) 31 | if err != nil { 32 | return renderer{}, fmt.Errorf("glob: %w", err) 33 | } 34 | 35 | for _, name := range files { 36 | data, err := afero.ReadFile(fs, name) 37 | if err != nil { 38 | return renderer{}, fmt.Errorf("ReadFile: %w", err) 39 | } 40 | 41 | tpl, err := template.New(name).Funcs(funcMap).Parse(string(data)) 42 | if err != nil { 43 | return renderer{}, fmt.Errorf("parse: %w", err) 44 | } 45 | 46 | newRenderer.tpls[name] = tpl 47 | } 48 | 49 | return newRenderer, nil 50 | } 51 | 52 | // Substitute tries to correct collisions with go keywords. The data should be passed as a pointer. 53 | // The data will then be substituted into the template from static FS. 54 | func (s renderer) Substitute(name string, v interface{}) (string, error) { 55 | resolveCollisions(v) 56 | 57 | var buff strings.Builder 58 | tpl, ok := s.tpls[name] 59 | if !ok { 60 | return "", fmt.Errorf("template not found: %s", name) 61 | } 62 | 63 | if err := tpl.Execute(&buff, v); err != nil { 64 | return "", fmt.Errorf("execute: %w", err) 65 | } 66 | 67 | return buff.String(), nil 68 | } 69 | -------------------------------------------------------------------------------- /cmd/watcher/commands/watch.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | 9 | "github.com/SberMarket-Tech/grpc-wiremock/internal/usecases/watcher" 10 | ) 11 | 12 | type WatchArgs struct { 13 | mocksPath string 14 | 15 | domainsPath string 16 | } 17 | 18 | func watchCommand(subCommands ...*cobra.Command) *cobra.Command { 19 | var args WatchArgs 20 | 21 | command := &cobra.Command{ 22 | Use: "watch", 23 | RunE: func(cmd *cobra.Command, _ []string) error { 24 | requests, err := createInputs(args) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | runner := watcher.NewRunner(os.Stdout) 30 | 31 | if err = runner.Watch(cmd.Context(), requests...); err != nil { 32 | return err 33 | } 34 | 35 | return nil 36 | }, 37 | } 38 | 39 | command.Flags().StringVar(&args.mocksPath, "mocks", "", "Directory with mocks") 40 | command.Flags().StringVar(&args.domainsPath, "domains", "", "Directory with domain directories") 41 | 42 | command.AddCommand(subCommands...) 43 | 44 | return command 45 | } 46 | 47 | func createInputs(args WatchArgs) ([]watcher.WatchRequest, error) { 48 | if len(args.mocksPath) == 0 && len(args.domainsPath) == 0 { 49 | return nil, fmt.Errorf("watch command: at least one parameter must be set") 50 | } 51 | 52 | var watchers []watcher.WatchRequest 53 | 54 | if len(args.mocksPath) > 0 { 55 | watchers = append(watchers, watcher.WatchRequest{ 56 | Path: args.mocksPath, 57 | Name: watcher.MocksWatcher, 58 | }) 59 | } 60 | 61 | if len(args.domainsPath) > 0 { 62 | watchers = append(watchers, watcher.WatchRequest{ 63 | Path: args.domainsPath, 64 | Name: watcher.DomainsWatcher, 65 | }) 66 | } 67 | 68 | return watchers, nil 69 | } 70 | -------------------------------------------------------------------------------- /internal/usecases/confgen/options.go: -------------------------------------------------------------------------------- 1 | package confgen 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/afero" 7 | 8 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/fsutils" 9 | "github.com/SberMarket-Tech/grpc-wiremock/static" 10 | ) 11 | 12 | const ( 13 | nginxOption = "nginx" 14 | supervisordOption = "supervisord" 15 | ) 16 | 17 | var staticFS = static.FromEmbed() 18 | 19 | type option struct { 20 | name string 21 | enable bool 22 | outputPath string 23 | 24 | prepareFunctions []preparator 25 | } 26 | 27 | type preparator func(afero.Fs, string) error 28 | 29 | func withOption(name, outputPath string) option { 30 | return option{enable: true, name: name, outputPath: outputPath} 31 | } 32 | 33 | func withOptionAndPreparation(name, outputPath string, prepareF ...preparator) option { 34 | return option{enable: true, name: name, outputPath: outputPath, prepareFunctions: prepareF} 35 | } 36 | 37 | func WithNGINX(outputPath string) option { 38 | return withOption(nginxOption, outputPath) 39 | } 40 | 41 | func WithSupervisord(outputPath string, prepareF ...preparator) option { 42 | return withOptionAndPreparation(supervisordOption, outputPath, prepareF...) 43 | } 44 | 45 | func WithDefaultSupervisordConfig(fs afero.Fs, path string) error { 46 | const templatePath = "supervisord/default-config-path" 47 | 48 | if err := fsutils.CopyDir(staticFS, fs, templatePath, path, false); err != nil { 49 | return fmt.Errorf("prepare default supervisord config: %w", err) 50 | } 51 | 52 | return nil 53 | } 54 | 55 | type Options []option 56 | 57 | func (o Options) Paths() []string { 58 | var paths []string 59 | 60 | for _, option := range o { 61 | paths = append(paths, option.outputPath) 62 | } 63 | 64 | return paths 65 | } 66 | -------------------------------------------------------------------------------- /pkg/svctesting/svcrunner/runner.go: -------------------------------------------------------------------------------- 1 | package svcrunner 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "syscall" 10 | ) 11 | 12 | func Run(ctx context.Context, path, port string) (func(), error) { 13 | process, err := start(ctx, path, port) 14 | if err != nil { 15 | return nil, fmt.Errorf("process start is failed: %w", err) 16 | } 17 | 18 | turnOffF := func() { 19 | log.Printf("Process %d and his whole family "+ 20 | "are on their way to Valhalla", process.Pid) 21 | 22 | killGroup(process) 23 | } 24 | 25 | log.Printf("Process %d is started\n", process.Pid) 26 | 27 | return turnOffF, nil 28 | } 29 | 30 | func killGroup(process *os.Process) { 31 | if process == nil { 32 | log.Println("Process is nil. Can't stop the process") 33 | return 34 | } 35 | 36 | negativePIDToKillEntireGroup := -process.Pid 37 | 38 | // kill the entire group with child processes 39 | if err := syscall.Kill(negativePIDToKillEntireGroup, syscall.SIGKILL); err != nil { 40 | log.Printf("failed to kill child process %d\n", process.Pid) 41 | } 42 | } 43 | 44 | func start(ctx context.Context, path, port string) (*os.Process, error) { 45 | command := exec.CommandContext(ctx, "make", "run", "-C", path) 46 | command.Env = createEnvs(port) 47 | 48 | command.Stdout = os.Stdout 49 | command.Stderr = os.Stderr 50 | 51 | command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} 52 | 53 | if err := command.Start(); err != nil { 54 | return nil, fmt.Errorf("run cmd: %w", err) 55 | } 56 | 57 | return command.Process, nil 58 | } 59 | 60 | func createEnvs(port string) []string { 61 | const portEnv = "GRPC_TO_HTTP_PROXY_PORT" 62 | 63 | env := fmt.Sprintf("%s=%s", portEnv, port) 64 | envs := os.Environ() 65 | 66 | return append(envs, env) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/utils/strutils/strutils.go: -------------------------------------------------------------------------------- 1 | package strutils 2 | 3 | import ( 4 | "bytes" 5 | "path/filepath" 6 | "regexp" 7 | "sort" 8 | "strings" 9 | "unicode" 10 | ) 11 | 12 | var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") 13 | var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") 14 | 15 | func ToSnakeCase(input string) string { 16 | output := matchFirstCap.ReplaceAllString(input, "${1}_${2}") 17 | output = matchAllCap.ReplaceAllString(output, "${1}_${2}") 18 | output = strings.ReplaceAll(output, "-", "_") 19 | output = strings.ToLower(output) 20 | return strings.ReplaceAll(output, ".", "_") 21 | } 22 | 23 | func ToCamelCase(input string) string { 24 | res := bytes.NewBuffer(nil) 25 | capNext := true 26 | 27 | for _, v := range input { 28 | if unicode.IsUpper(v) { 29 | res.WriteRune(v) 30 | capNext = false 31 | continue 32 | } 33 | 34 | if unicode.IsDigit(v) { 35 | res.WriteRune(v) 36 | capNext = true 37 | continue 38 | } 39 | 40 | if unicode.IsLower(v) { 41 | if capNext { 42 | res.WriteRune(unicode.ToUpper(v)) 43 | } else { 44 | res.WriteRune(v) 45 | } 46 | capNext = false 47 | continue 48 | } 49 | 50 | capNext = true 51 | } 52 | 53 | return res.String() 54 | } 55 | 56 | func ToPackageName(input string) string { 57 | return ToSnakeCase(filepath.Base(input)) 58 | } 59 | 60 | func UniqueAndSorted(values ...string) []string { 61 | uniq := map[string]struct{}{} 62 | for _, value := range values { 63 | uniq[value] = struct{}{} 64 | } 65 | 66 | var uniqValues []string 67 | for value := range uniq { 68 | uniqValues = append(uniqValues, value) 69 | } 70 | 71 | sort.Slice(uniqValues, func(i, j int) bool { 72 | return uniqValues[i] < uniqValues[j] 73 | }) 74 | 75 | return uniqValues 76 | } 77 | -------------------------------------------------------------------------------- /dev/example/certs/mockCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA69D2GEMzCspD0Fj9ZgWIHuhWikmUOslV5ioQuv3kBkSb/Sq9 3 | rurNR6R+hWhoErwVYQzQjtbJd8jxtm7VT79tyoJCJMPRYAA+dwMhQ87HQJg86syj 4 | SGxPoQq8drkXvAYOaND/Y6qmvXxAq3GMUru+AUx69jbpfRNeHqBIDodyNzXajZr3 5 | bQgwb3FqrWqzLGqXeFkSCe2BwqVdIz6eK/3r9Zkuzm2bISkaV1LPquP+BXDoHLYh 6 | j5gBKH8sMfOje96Wc5kI6AWUOa1+8MPABP1k9zxd/g8IigLC9fkckECgkCf37H9C 7 | q1mfhABCDgLSoYUNj0MtlPAJLk2PdielFdLIowIDAQABAoIBAQDmvTpvlyFUh4E/ 8 | VjPsJbGBfJlhCont058nbTWdolwB/hQb7jxX8QwX886Qvw5Gdp78rUtM2MGdYZOR 9 | jHVQaOEG69nIm4o9Qz/bt8ZO0P+KJuxEWyPR85ke3KB4Erg0lEB5MzcR0KMCeZfV 10 | qW9CbM2H+8u9KxHf/plzoiWfgcyyUzC9IKLYiZTpead8q30w6aGWkNgkU+6Zas4D 11 | a9KhnN4WnkUP/j7V1xAHBGZn2piBn0ZtRfPBgh8Fzmn+5k7tgknG0IX5J7xqEvcP 12 | Yv3jL64e8kQq9IiQ/WhZP/0E31senVAulMFdx6qUCyUJnmDxUqchKEAWuCXFCu9/ 13 | MyrDaRKxAoGBAPJoyBR6MHMt/gG4uSJFP75D4n58vVCHJdM4JYFBZEbCbBhigKWk 14 | uGWxbDktRxjMD7NRVBmATG7NYBhh7TREYC3k9RPWerFZwp6S9sfluVESOZP7X2PK 15 | s9tHUZEsZhaYA8iccL+bd7/UHjVmaR9WuWxE/W0vAmpxz61xizYlaPcvAoGBAPkJ 16 | jU25xwGlEYTT4d8ZdQMvfLzHFPx1e4A/nMP7VEl8jNw9NFGyOxNlrnPiwwDAyTee 17 | Y9b9khIe5fFSwCQ6qJ/JvSkYawvwR54IFtWvanX+s+hvB64dYQ5QyCzYQUfcDQkD 18 | NlRseM6kSnrBP4wvtNVGyzY57xI9eZfZ+vem0qjNAoGAU+KssmFiWMlmqAPblR9v 19 | opDtVSYngTFzhndcizEs2Uu4imuz5x5sbUyLjfG8M7mSCVSwjK6YRJOvmGBy7G5o 20 | 9hjQMpA7HxpXvfZPjfdSPNrYr/wCE4921AAHGvzJSzJJxOdWIQhU1T47QfFxmVWy 21 | juQE6v6kkF9YJcYwDYm+nCMCgYEAgJv7OlmnuWS60GLv8dxevn9mpsMVRm+RkSDI 22 | 37lKxQyqXg7Jlys5MQJA/p3r4Nau06dbniqjpfIJBACdM9iLPsHbi36NwcOb6LP6 23 | vu3xR/B6jp3QVI3dkpwLVIV16RtuK+pZw22lUeosgIqOO+JjPAVsapuuqDfRNKeg 24 | 4QZpIj0CgYBzfHuvf4dSALFpNEcmMymj1Atrk5UGXpfl0G0LN293mqC34/ZwFeBi 25 | gbb14mW3/i6n4oddgj83042iC9JZiV2Id0bkrqoFokTrMlvES1Sde5OT5pqavakg 26 | hVN8bfO/af5b78ErzNzkkrAuiBKry8NZzVXQXZLk7JfyQrsGProGVA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /static/proxy/files/method-client-streaming.go.tpl: -------------------------------------------------------------------------------- 1 | package {{ .PackageHeader }} 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | 9 | "google.golang.org/grpc/status" 10 | "google.golang.org/protobuf/encoding/protojson" 11 | 12 | "grpc-proxy/pkg/wiremock" 13 | 14 | {{ range .GoPackages }} 15 | "{{ . }}" 16 | {{- end }} 17 | ) 18 | 19 | func (p *Service) {{ .Method }}(stream {{ .MethodPackage }}.{{ .Service }}_{{ .Method }}Server) error { 20 | const url = "{{ .URL }}" 21 | 22 | unmarshalAndSend := func(responseBody []byte) error { 23 | var protoResponse {{ .MethodOutPackage }}.{{ .MethodOutName }} 24 | if processErr := protojson.Unmarshal(responseBody, &protoResponse); processErr != nil { 25 | return status.Error(http.StatusBadGateway, fmt.Sprintf("marshal json object to proto: %v", processErr)) 26 | } 27 | if processErr := stream.SendAndClose(&protoResponse); processErr != nil { 28 | return processErr 29 | } 30 | return nil 31 | } 32 | 33 | defaultRequest, err := wiremock.DefaultRequest(stream.Context(), url, bytes.NewReader([]byte{})) 34 | if err != nil { 35 | return status.Error(http.StatusBadGateway, fmt.Sprintf("create http request: %v", err)) 36 | } 37 | 38 | httpResponseBody, streamSize, err := wiremock.DoRequestWithStreamSize(defaultRequest) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | streamCursor := 1 44 | 45 | for { 46 | req, errReceive := stream.Recv() 47 | if errReceive != nil && errReceive == io.EOF { 48 | return unmarshalAndSend(httpResponseBody) 49 | } 50 | if errReceive != nil { 51 | return errReceive 52 | } 53 | if streamCursor >= streamSize { 54 | return unmarshalAndSend(httpResponseBody) 55 | } 56 | if req == nil { 57 | continue 58 | } 59 | streamCursor++ 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/models/basecontract/unifier/unify.go: -------------------------------------------------------------------------------- 1 | package unifier 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/spf13/afero" 9 | 10 | contract "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/basecontract" 11 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/basecontract/unifier/openapi" 12 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/basecontract/unifier/proto" 13 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/sourcer/types" 14 | ) 15 | 16 | type unifier interface { 17 | Unify(context.Context, contract.SetOfContracts, string) error 18 | } 19 | 20 | type compiler interface { 21 | CompileToOpenAPI(context.Context, contract.Contract, string, io.Writer) error 22 | } 23 | 24 | type baseUnifier struct { 25 | fs afero.Fs 26 | 27 | compiler compiler 28 | 29 | logs io.Writer 30 | } 31 | 32 | func NewUnifier(fs afero.Fs, compiler compiler, logs io.Writer) *baseUnifier { 33 | return &baseUnifier{fs: fs, logs: logs, compiler: compiler} 34 | } 35 | 36 | func (u *baseUnifier) Unify(ctx context.Context, contracts contract.SetOfContracts, path string) error { 37 | specificUnifier, err := u.getUnifier(u.fs, contracts, u.compiler) 38 | if err != nil { 39 | return fmt.Errorf("get unifier: %w", err) 40 | } 41 | 42 | if err := specificUnifier.Unify(ctx, contracts, path); err != nil { 43 | return fmt.Errorf("unify: %w", err) 44 | } 45 | 46 | return nil 47 | } 48 | 49 | func (u *baseUnifier) getUnifier(fs afero.Fs, contracts contract.SetOfContracts, compiler compiler) (unifier, error) { 50 | switch contracts.FileType() { 51 | case types.ProtoType: 52 | return proto.NewUnifier(fs, compiler, u.logs), nil 53 | 54 | case types.OpenAPIType: 55 | return openapi.NewUnifier(fs), nil 56 | } 57 | 58 | return nil, fmt.Errorf("no available unifier") 59 | } 60 | -------------------------------------------------------------------------------- /pkg/generators/proxy/project.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | 7 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/models/protocontract" 8 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/utils/fsutils" 9 | "github.com/SberMarket-Tech/grpc-wiremock/static" 10 | ) 11 | 12 | const ( 13 | mainGoTemplatePath = "proxy/files/main.go.tpl" 14 | projectTemplateDir = "proxy/template/layout" 15 | ) 16 | 17 | // GenerateProject generates golang project based on the template. 18 | // And calls proto compiler to compile packages based on provided proto contracts. 19 | func (g proxyGenerator) GenerateProject(contracts protocontract.SetOfContracts) error { 20 | return g.generateProject(contracts, projectTemplateDir) 21 | } 22 | 23 | func (g proxyGenerator) generateProject(contracts protocontract.SetOfContracts, templatePath string) error { 24 | staticFS := static.FromEmbed() 25 | 26 | if err := fsutils.CopyDir(staticFS, g.fs, templatePath, g.output, false); err != nil { 27 | return fmt.Errorf("copy template: %w", err) 28 | } 29 | 30 | fileToPath := map[string]struct { 31 | path string 32 | substitution interface{} 33 | }{ 34 | "cmd/main.go": { 35 | path: mainGoTemplatePath, 36 | substitution: substitutionForProject(contracts), 37 | }, 38 | } 39 | 40 | for outputFilePath, template := range fileToPath { 41 | content, err := g.renderer.Substitute(template.path, &template.substitution) 42 | if err != nil { 43 | return fmt.Errorf("substitute file: %s, err: %w", template.path, err) 44 | } 45 | 46 | pathInProject := filepath.Join(g.output, outputFilePath) 47 | 48 | if err = fsutils.WriteFile(g.fs, pathInProject, content); err != nil { 49 | return fmt.Errorf("write main.go file: %w", err) 50 | } 51 | } 52 | 53 | return nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/generators/certificates/cert_test.go: -------------------------------------------------------------------------------- 1 | package certificates 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/generators/certificates/test" 12 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/wiremock/configopener" 13 | ) 14 | 15 | func Test_testCertificates(t *testing.T) { 16 | t.Run("with correct certificates", func(t *testing.T) { 17 | opener := configopener.New(staticFS, "tests/data/wiremock/configs/one-service") 18 | g := NewGenerator(staticFS, opener) 19 | 20 | caCertPair, certPair, err := g.generate([]string{"awesome"}, "") 21 | require.NoError(t, err) 22 | 23 | serverConfig, clientConfig, err := createTLSConfig(caCertPair, certPair) 24 | require.NoError(t, err) 25 | 26 | err = runTest(test.NewCertsTester(serverConfig, clientConfig)) 27 | require.NoError(t, err) 28 | }) 29 | } 30 | 31 | func runTest(t test.CertsTester) error { 32 | t.Server.StartTLS() 33 | defer t.Server.Close() 34 | 35 | if err := t.DoTestRequest(); err != nil { 36 | return err 37 | } 38 | 39 | return nil 40 | } 41 | 42 | func createTLSConfig(caPair, certificatePair pair) (*tls.Config, *tls.Config, error) { 43 | tlsCertificate, err := tls.X509KeyPair(certificatePair.certificate, certificatePair.privateKey) 44 | if err != nil { 45 | return nil, nil, fmt.Errorf("tls pair: %w", err) 46 | } 47 | 48 | pool := x509.NewCertPool() 49 | 50 | if !pool.AppendCertsFromPEM(caPair.certificate) { 51 | return nil, nil, fmt.Errorf("pool doesn't contain certificate authority") 52 | } 53 | 54 | clientTLSConf := tls.Config{RootCAs: pool} 55 | serverTLSConf := tls.Config{Certificates: []tls.Certificate{tlsCertificate}} 56 | 57 | return &serverTLSConf, &clientTLSConf, nil 58 | } 59 | -------------------------------------------------------------------------------- /dev/example/internal/wearable_service/beats_per_minute.go: -------------------------------------------------------------------------------- 1 | package wearable_service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/nktch1/wearable/pkg/clients/push_sender" 10 | "github.com/nktch1/wearable/pkg/server/wearable" 11 | ) 12 | 13 | const expectedStatusCode = 1126 14 | 15 | func (p *Service) BeatsPerMinute(in *wearable.BeatsPerMinuteRequest, stream wearable.WearableService_BeatsPerMinuteServer) error { 16 | const batchSize = 30 17 | 18 | for idx := 0; idx < batchSize; idx++ { 19 | heartRate := newRandInt() 20 | 21 | fmt.Printf("current heart rate: %d\n", heartRate) 22 | 23 | if somethingIsGoingWrong(heartRate) { 24 | fmt.Printf("\t something is going wrong, notify\n") 25 | 26 | notifyResponse, err := p.sender.Notify(context.Background(), &push_sender.NotifyRequest{ 27 | Uuid: "some_uuid", 28 | Message: "Something is going wrong!", 29 | }) 30 | 31 | if err != nil { 32 | return fmt.Errorf("notify: %w", err) 33 | } 34 | 35 | fmt.Printf("\t notifying status is %d\n", notifyResponse.Status) 36 | 37 | if notifyResponse.Status != expectedStatusCode { 38 | return fmt.Errorf("status code %d is incorrect, expected: %d", notifyResponse.Status, expectedStatusCode) 39 | } 40 | } 41 | 42 | response := wearable.BeatsPerMinuteResponse{ 43 | Value: newRandInt(), 44 | Minute: uint32(idx), 45 | } 46 | 47 | if err := stream.Send(&response); err != nil { 48 | return fmt.Errorf("send: %w", err) 49 | } 50 | 51 | time.Sleep(time.Second) 52 | } 53 | 54 | fmt.Println("batch processing is done!") 55 | 56 | return nil 57 | } 58 | 59 | func newRandInt() uint32 { 60 | min := 30 61 | max := 160 62 | return uint32(rand.Intn(max-min) + min) 63 | } 64 | 65 | func somethingIsGoingWrong(heartRate uint32) bool { 66 | return heartRate < 60 || heartRate > 140 67 | } 68 | -------------------------------------------------------------------------------- /pkg/models/basecontract/from_proto.go: -------------------------------------------------------------------------------- 1 | package basecontract 2 | 3 | import ( 4 | "fmt" 5 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/blacklist" 6 | "path/filepath" 7 | 8 | "github.com/jhump/protoreflect/desc" 9 | ) 10 | 11 | // FromProtoDescriptor converts proto descriptor into Contract model. 12 | func FromProtoDescriptor(descriptor *desc.FileDescriptor, protoPath string) (Contract, error) { 13 | if descriptor == nil { 14 | return Contract{}, fmt.Errorf("descriptor is nil") 15 | } 16 | 17 | contract := Contract{ 18 | descriptor: descriptor, 19 | ContractHasMethods: hasMethods(descriptor), 20 | HeaderPath: filepath.Join(protoPath, descriptor.GetName()), 21 | 22 | ImportsPaths: protoImports(descriptor, protoPath), 23 | } 24 | 25 | return contract, nil 26 | } 27 | 28 | func protoImports(descriptor *desc.FileDescriptor, protoPath string) []string { 29 | var imports []string 30 | 31 | for _, path := range getImports(descriptor) { 32 | if blacklist.IsDeliveredWithProtoc(path) { 33 | continue 34 | } 35 | 36 | if blacklist.IsGoogleAPIContract(path) { 37 | continue 38 | } 39 | 40 | imports = append(imports, filepath.Join(protoPath, path)) 41 | } 42 | 43 | return imports 44 | } 45 | 46 | func getImports(descriptor *desc.FileDescriptor) []string { 47 | var all []string 48 | traverse(descriptor, &all) 49 | 50 | var paths []string 51 | for _, path := range all { 52 | paths = append(paths, path) 53 | } 54 | 55 | return paths 56 | } 57 | 58 | func traverse(desc *desc.FileDescriptor, all *[]string) { 59 | for _, descriptor := range desc.GetDependencies() { 60 | *all = append(*all, descriptor.GetName()) 61 | traverse(descriptor, all) 62 | } 63 | } 64 | 65 | func hasMethods(descriptor *desc.FileDescriptor) bool { 66 | for _, service := range descriptor.GetServices() { 67 | if len(service.GetMethods()) > 0 { 68 | return true 69 | } 70 | } 71 | 72 | return false 73 | } 74 | -------------------------------------------------------------------------------- /pkg/wiremock/configopener/open.go: -------------------------------------------------------------------------------- 1 | package configopener 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "path/filepath" 7 | "strconv" 8 | "strings" 9 | 10 | supervisorconf "github.com/ochinchina/supervisord/config" 11 | "github.com/sirupsen/logrus" 12 | "github.com/spf13/afero" 13 | 14 | "github.com/SberMarket-Tech/grpc-wiremock/pkg/wiremock/config" 15 | ) 16 | 17 | type opener struct { 18 | fs afero.Fs 19 | 20 | path string 21 | } 22 | 23 | func New(fs afero.Fs, path string) *opener { 24 | return &opener{fs: fs, path: path} 25 | } 26 | 27 | func (o *opener) Open() (config.Wiremock, error) { 28 | const configName = "supervisord.conf" 29 | 30 | var wiremock config.Wiremock 31 | 32 | path := filepath.Join(o.path, configName) 33 | supervisordConf := supervisorconf.NewConfig(path) 34 | 35 | logrus.SetOutput(io.Discard) 36 | 37 | _, err := supervisordConf.Load() 38 | if err != nil { 39 | return wiremock, err 40 | } 41 | 42 | for _, p := range supervisordConf.GetPrograms() { 43 | envs := convertEnvs(p.GetEnv("environment")) 44 | 45 | name, exists := envs["NAME"] 46 | if !exists { 47 | continue 48 | } 49 | 50 | root, exists := envs["ROOT"] 51 | if !exists { 52 | continue 53 | } 54 | 55 | port, exists := envs["PORT"] 56 | if !exists { 57 | continue 58 | } 59 | 60 | portInt, err := strconv.Atoi(port) 61 | if err != nil { 62 | continue 63 | } 64 | 65 | wiremock.Services = append(wiremock.Services, config.Service{ 66 | Name: name, RootDir: root, Port: portInt}) 67 | } 68 | 69 | return wiremock, nil 70 | } 71 | 72 | func convertEnvs(envs []string) map[string]string { 73 | const separator = "=" 74 | converted := map[string]string{} 75 | 76 | for _, env := range envs { 77 | parts := strings.Split(env, separator) 78 | if len(parts) != 2 { 79 | log.Println("parse env error:", env) 80 | continue 81 | } 82 | 83 | converted[parts[0]] = parts[1] 84 | } 85 | 86 | return converted 87 | } 88 | --------------------------------------------------------------------------------