diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cbcd08d..e43ec7b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,7 +38,7 @@ jobs: uses: codecov/codecov-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} - file: test/coverage.out + file: coverage.out binary: name: Build binary diff --git a/.gitignore b/.gitignore index b22e762..9d5bcf3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.idea /vendor +/coverage.out mock_*_test.go wire_gen.go diff --git a/Makefile b/Makefile index 81de697..d8f99fa 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ all: cleanup vendor wire lint test build .PHONY: cleanup cleanup: @rm ${PWD}/bin/${APP_NAME}* || true - @rm ${PWD}/tests/coverage.out || true + @rm ${PWD}/coverage.out || true @find ${PWD} -type f -name "wire_gen.go" -delete @find ${PWD} -type f -name "mock_*_test.go" -delete @rm -r ${PWD}/vendor || true @@ -80,7 +80,7 @@ lint: .PHONY: test test: - @rm -r ${PWD}/test/coverage.out || true + @rm -r ${PWD}/coverage.out || true @docker run --rm \ -v ${PWD}:/project \ -w /project \ @@ -89,7 +89,7 @@ test: -race \ -mod vendor \ -covermode=atomic \ - -coverprofile=/project/test/coverage.out \ + -coverprofile=/project/coverage.out \ /project/... .PHONY: build @@ -143,7 +143,6 @@ endif @docker login -u ${DOCKER_USER} -p ${DOCKER_PASSWORD} ${DOCKER_REGISTRY} @docker push ${DOCKER_IMAGE}:${DOCKER_TAG} - .PHONY: mockery mockery: ifndef MOCKERY_SOURCE_DIR diff --git a/cmd/pinchy/internal/command.go b/cmd/pinchy/internal/command.go index 978da8c..9f795ce 100644 --- a/cmd/pinchy/internal/command.go +++ b/cmd/pinchy/internal/command.go @@ -29,11 +29,17 @@ func NewCommand(version string) *cobra.Command { Use: sourceProvider.Name(), Short: fmt.Sprintf(`Fetch data from source "%s"`, sourceProvider.Name()), } + if sourceProvider.Deprecated() { + sourceCmd.Deprecated = fmt.Sprintf(`source "%s" is deprecated`, sourceProvider.Name()) + } for _, registryProvider := range registry.GetProviderList() { registryCmd := &cobra.Command{ Use: registryProvider.Name(), Short: fmt.Sprintf(`Save data in registry "%s"`, registryProvider.Name()), } + if registryProvider.Deprecated() { + registryCmd.Deprecated = fmt.Sprintf(`registry "%s" is deprecated`, registryProvider.Name()) + } onceCommand := &cobra.Command{ Use: `once`, Short: `Run main process only once: sync and return result`, diff --git a/configs/source/file/consul-agent.yml b/configs/source/file/consul-agent.yml new file mode 100644 index 0000000..af1cb98 --- /dev/null +++ b/configs/source/file/consul-agent.yml @@ -0,0 +1,19 @@ +- name: service-name + address: 127.0.0.1 + id: service-id-1 + port: 80 + tags: + - tag-1 + - tag-2 + meta: + key: value + +- name: service-name + address: 127.0.0.2 + id: service-id-2 + port: 80 + tags: + - tag-1 + - tag-2 + meta: + key: value diff --git a/configs/source/file/consul-catalog.yml b/configs/source/file/consul-catalog.yml new file mode 100644 index 0000000..89b9bf7 --- /dev/null +++ b/configs/source/file/consul-catalog.yml @@ -0,0 +1,25 @@ +- name: service-name + address: 127.0.0.1 + id: service-id + port: 80 + tags: + - tag-1 + - tag-2 + meta: + key: value + node: + node: node-1 + address: 127.0.0.100 + +- name: service-name + address: 127.0.0.2 + id: service-id-2 + port: 80 + tags: + - tag-1 + - tag-2 + meta: + key: value + node: + node: node-1 + address: 127.0.0.100 diff --git a/configs/source/file/example.yml b/configs/source/file/example.yml deleted file mode 100644 index 04dbd31..0000000 --- a/configs/source/file/example.yml +++ /dev/null @@ -1,9 +0,0 @@ -- name: service-name - address: 127.0.0.1 - id: service-id - port: 80 - tags: - - tag-1 - - tag-2 - meta: - key: value diff --git a/deployments/docker-compose/pinchy/docker-compose.yml b/deployments/docker-compose/pinchy/docker-compose.yml index 5fe503b..a5063d4 100644 --- a/deployments/docker-compose/pinchy/docker-compose.yml +++ b/deployments/docker-compose/pinchy/docker-compose.yml @@ -1,15 +1,28 @@ version: '3' services: - pinchy: - image: insidieux/pinchy:v1.0.0 + pinchy-1: + image: insidieux/pinchy:v1.1.0 volumes: - ./configs/source/file:/etc/pinchy command: - 'file' - - 'consul' + - 'consul-agent' - '--source.path' - - '/etc/pinchy/example.yml' + - '/etc/pinchy/consul-agent.yml' - '--registry.address' - 'http://consul:8500' - 'once' + pinchy-2: + image: insidieux/pinchy:v1.1.0 + volumes: + - ./configs/source/file:/etc/pinchy + command: + - 'file' + - 'consul-catalog' + - '--source.path' + - '/etc/pinchy/consul-catalog.yml' + - '--registry.address' + - 'http://consul:8500' + - 'once' + diff --git a/docs/README.md b/docs/README.md index d51f878..d1a253d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,4 +4,4 @@ - [Contributing guide][] [User guide]: ./user-guide.md -[Contributing guide]: ./contributing-guide.md +[Contributing guide]: ./contributing.md diff --git a/docs/contributing.md b/docs/contributing.md index 1dd79af..98d9b13 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -19,4 +19,4 @@ - Add new sub directory to `/internal/extension/{source,registry}` - Put wiring code for dynamic load implementations: - Register implementation of `ProviderInterface` at `init` function - - Add anonymous import to `/cmd/pinchy/modules.go` + - Add anonymous import to `/cmd/pinchy/internal/modules.go` diff --git a/docs/registry/consul.md b/docs/registry/consul.md index a9ab0a3..89526f4 100644 --- a/docs/registry/consul.md +++ b/docs/registry/consul.md @@ -1,7 +1,23 @@ # Pinchy registry "Consul" +## Available variations + +### consul-agent [**Deprecated!**] + +Registry work with Consul agent HTTP API + +### consul-agent + +Registry work with Consul agent HTTP API + +### consul-catalog + +Registry work with Consul catalog HTTP API + + ## Available flags ``` --registry.address string Consul http api address (default "127.0.0.1:8500") +--registry.tag string Common service tag added for all registered service (default "pinchy") ``` diff --git a/docs/source/file.md b/docs/source/file.md index 475f878..d1c87b8 100644 --- a/docs/source/file.md +++ b/docs/source/file.md @@ -8,4 +8,6 @@ ## services.yml example -Example services.yml file be found in [configs](./../../configs/source/file/example.yml) file. +Example services.yml file be found in configs directory: +* [Consul agent registry](../../configs/source/file/consul-agent.yml) file. +* [Consul catalog registry](../../configs/source/file/consul-catalog.yml) file. diff --git a/go.mod b/go.mod index 8d1f520..1877dff 100644 --- a/go.mod +++ b/go.mod @@ -4,22 +4,17 @@ go 1.15 require ( github.com/agrea/ptr v0.0.0-20180711073057-77a518d99b7b - github.com/google/wire v0.4.0 - github.com/hashicorp/consul/api v1.7.0 - github.com/hashicorp/go-cleanhttp v0.5.1 + github.com/google/wire v0.5.0 + github.com/hashicorp/consul/api v1.8.1 github.com/pkg/errors v0.9.1 github.com/sethvargo/go-signalcontext v0.1.0 - github.com/sirupsen/logrus v1.2.0 - github.com/spf13/afero v1.2.2 - github.com/spf13/cast v1.3.0 + github.com/sirupsen/logrus v1.7.0 + github.com/spf13/afero v1.5.1 + github.com/spf13/cast v1.3.1 github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.7.0 github.com/thoas/go-funk v0.7.0 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476 // indirect - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index 3f79ce3..cac02cb 100644 --- a/go.sum +++ b/go.sum @@ -61,7 +61,6 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= @@ -72,10 +71,9 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TUH7k= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/google/wire v0.4.0 h1:kXcsA/rIGzJImVqPdhfnr6q0xsS9gU0515q1EPpJ9fE= -github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= +github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -85,11 +83,11 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.7.0 h1:tGs8Oep67r8CcA2Ycmb/8BLBcJ70St44mF2X10a/qPg= -github.com/hashicorp/consul/api v1.7.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg= +github.com/hashicorp/consul/api v1.8.1 h1:BOEQaMWoGMhmQ29fC26bi0qb7/rId9JzZP2V0Xmx7m8= +github.com/hashicorp/consul/api v1.8.1/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.6.0 h1:FfhMEkwvQl57CildXJyGHnwGGM4HMODGyfjGwNM1Vdw= -github.com/hashicorp/consul/sdk v0.6.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= +github.com/hashicorp/consul/sdk v0.7.0 h1:H6R9d008jDcHPQPAqPNuydAshJ4v5/8URdFnUvK/+sc= +github.com/hashicorp/consul/sdk v0.7.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= @@ -113,7 +111,6 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -126,8 +123,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.3 h1:AVF6JDQQens6nMHT9OGERBvK0f8rPrAGILnsKLr6lzM= -github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -138,8 +135,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= @@ -187,6 +184,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -209,20 +207,21 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/sethvargo/go-signalcontext v0.1.0 h1:3IU7HOlmRXF0PSDf85C4nJ/zjYDjF+DS+LufcKfLvyk= github.com/sethvargo/go-signalcontext v0.1.0/go.mod h1:PXu9UmR2f7mmp8kEwgkKmaDbxq/PbqixkiC66WIkkWE= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg= +github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= @@ -239,8 +238,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y= @@ -258,9 +257,9 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -295,8 +294,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476 h1:E7ct1C6/33eOdrGZKMoyntcEvs2dwZnDe30crG5vpYU= -golang.org/x/net v0.0.0-20200519113804-d87ec0cfa476/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -306,8 +303,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -330,12 +325,11 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -392,11 +386,11 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/extension/registry/consul/provider.go b/internal/extension/registry/consul/provider.go index 52ca31a..c73efe2 100644 --- a/internal/extension/registry/consul/provider.go +++ b/internal/extension/registry/consul/provider.go @@ -1,28 +1,30 @@ package consul import ( - "net/http" - pkgConsul "github.com/insidieux/pinchy/pkg/core/registry/consul" "github.com/hashicorp/consul/api" "github.com/insidieux/pinchy/internal/extension/registry" + "github.com/insidieux/pinchy/pkg/core/registry/consul/agent" + "github.com/insidieux/pinchy/pkg/core/registry/consul/catalog" "github.com/pkg/errors" "github.com/spf13/pflag" "github.com/spf13/viper" ) const ( - registryName = `consul` + registryName = `consul` + registryAgentName = `consul-agent` + registryCatalogName = `consul-catalog` flagConsulAddress = `address` - - defaultCommonTag = `pinchy` + flagTag = `tag` ) type ( client interface { Agent() *api.Agent + Catalog() *api.Catalog } factory func(*api.Config) (*api.Client, error) ) @@ -30,12 +32,31 @@ type ( func init() { set := pflag.NewFlagSet(registryName, pflag.ExitOnError) set.String(registry.MakeFlagName(flagConsulAddress), `127.0.0.1:8500`, `Consul http api address`) - if err := registry.Register(registryName, set, NewRegistry); err != nil { + set.String(registry.MakeFlagName(flagTag), `pinchy`, `Common service tag added for all registered service`) + // register deprecated consul agent registry + if err := registry.Register(registryName, set, NewAgentRegistry, true); err != nil { + panic(err) + } + // register new consul agent registry + if err := registry.Register(registryAgentName, set, NewAgentRegistry, false); err != nil { + panic(err) + } + // register new consul catalog registry + if err := registry.Register(registryCatalogName, set, NewCatalogRegistry, false); err != nil { panic(err) } } -func provideClientConfig(v *viper.Viper, transport *http.Transport) (*api.Config, error) { +func provideTag(v *viper.Viper) (pkgConsul.Tag, error) { + flag := registry.MakeFlagName(flagTag) + tag := v.GetString(flag) + if tag == `` { + return ``, errors.Errorf(`Flag "%s" is required`, flag) + } + return pkgConsul.Tag(tag), nil +} + +func provideClientConfig(v *viper.Viper) (*api.Config, error) { flag := registry.MakeFlagName(flagConsulAddress) address := v.GetString(flag) if address == `` { @@ -44,7 +65,6 @@ func provideClientConfig(v *viper.Viper, transport *http.Transport) (*api.Config cfg := api.DefaultConfig() cfg.Address = address - cfg.Transport = transport return cfg, nil } @@ -60,6 +80,10 @@ func provideClient(cfg *api.Config, factory factory) (client, error) { return c, nil } -func provideAgent(c client) pkgConsul.Agent { +func provideAgent(c client) agent.Agent { return c.Agent() } + +func provideCatalog(c client) catalog.Catalog { + return c.Catalog() +} diff --git a/internal/extension/registry/consul/wire.go b/internal/extension/registry/consul/wire.go index a5b00f1..5131bfd 100644 --- a/internal/extension/registry/consul/wire.go +++ b/internal/extension/registry/consul/wire.go @@ -4,21 +4,35 @@ package consul import ( "github.com/google/wire" - "github.com/hashicorp/go-cleanhttp" "github.com/insidieux/pinchy/pkg/core" - "github.com/insidieux/pinchy/pkg/core/registry/consul" + "github.com/insidieux/pinchy/pkg/core/registry/consul/agent" + "github.com/insidieux/pinchy/pkg/core/registry/consul/catalog" "github.com/spf13/viper" ) -func NewRegistry(*viper.Viper) (core.Registry, func(), error) { - panic(wire.Build( - cleanhttp.DefaultPooledTransport, +var ( + wireSet = wire.NewSet( provideClientConfig, provideConsulClientFactory, provideClient, + provideTag, + ) +) + +func NewAgentRegistry(*viper.Viper) (core.Registry, func(), error) { + panic(wire.Build( + wireSet, provideAgent, - consul.NewRegistry, - wire.Value(consul.Tag(defaultCommonTag)), - wire.Bind(new(core.Registry), new(*consul.Registry)), + agent.NewRegistry, + wire.Bind(new(core.Registry), new(*agent.Registry)), + )) +} + +func NewCatalogRegistry(*viper.Viper) (core.Registry, func(), error) { + panic(wire.Build( + wireSet, + provideCatalog, + catalog.NewRegistry, + wire.Bind(new(core.Registry), new(*catalog.Registry)), )) } diff --git a/internal/extension/registry/extension.go b/internal/extension/registry/extension.go index cff997f..12e71c0 100644 --- a/internal/extension/registry/extension.go +++ b/internal/extension/registry/extension.go @@ -28,15 +28,17 @@ type ( Name() string Flags() *pflag.FlagSet Factory() Factory + Deprecated() bool } // ProviderList is helper custom type for handle registration and lookup for slice of ProviderInterface ProviderList []ProviderInterface provider struct { - name string - flags *pflag.FlagSet - factory Factory + name string + flags *pflag.FlagSet + factory Factory + deprecated bool } ) @@ -62,11 +64,12 @@ func MakeFlagName(name string) string { // Register new ProviderInterface implementation by name, flags and factory // Register returns error if ProviderInterface already registered with same name // Register returns error if ProviderInterface provide flags with incorrect name prefix -func Register(name string, flags *pflag.FlagSet, factory Factory) error { +func Register(name string, flags *pflag.FlagSet, factory Factory, deprecated bool) error { return providerList.register(&provider{ - name: name, - flags: flags, - factory: factory, + name: name, + flags: flags, + factory: factory, + deprecated: deprecated, }) } @@ -75,16 +78,21 @@ func (p *provider) Name() string { return p.name } -// Name return ProviderInterface implementation flags +// Flags return ProviderInterface implementation flags func (p *provider) Flags() *pflag.FlagSet { return p.flags } -// Name return ProviderInterface implementation factory +// Factory return ProviderInterface implementation factory func (p *provider) Factory() Factory { return p.factory } +// Deprecated return ProviderInterface implementation factory +func (p *provider) Deprecated() bool { + return p.deprecated +} + func (pl *ProviderList) register(p ProviderInterface) error { if ep, _ := pl.Lookup(p.Name()); ep != nil { return errors.Errorf(`registry provider with name "%s" has been already registered`, p.Name()) diff --git a/internal/extension/registry/extension_test.go b/internal/extension/registry/extension_test.go index 338e671..e4fbbe9 100644 --- a/internal/extension/registry/extension_test.go +++ b/internal/extension/registry/extension_test.go @@ -49,6 +49,10 @@ func TestProvider_Name(t *testing.T) { suite.Run(t, new(providerNameTestSuite)) } +func TestProvider_Deprecated(t *testing.T) { + suite.Run(t, new(providerDeprecatedTestSuite)) +} + func TestRegister(t *testing.T) { suite.Run(t, new(registerTestSuite)) } @@ -199,6 +203,17 @@ func (s *providerNameTestSuite) TestSuccess() { s.Equal(`name`, p.Name()) } +type providerDeprecatedTestSuite struct { + suite.Suite +} + +func (s *providerDeprecatedTestSuite) TestSuccess() { + p := &provider{ + deprecated: false, + } + s.False(p.Deprecated()) +} + type registerTestSuite struct { suite.Suite provider *MockProviderInterface @@ -211,7 +226,7 @@ func (s *registerTestSuite) SetupTest() { func (s *registerTestSuite) TestErrorProviderHasBeenRegistered() { *providerList = append(*providerList, &provider{name: `provider`}) - err := Register(`provider`, nil, nil) + err := Register(`provider`, nil, nil, false) s.Error(err) s.EqualError(err, `registry provider with name "provider" has been already registered`) } @@ -220,7 +235,7 @@ func (s *registerTestSuite) TestErrorProviderHasFlagsWithoutRequiredPrefix() { flags := pflag.NewFlagSet(`provider`, pflag.ExitOnError) flags.String(`wrong.flag`, `value`, `usage`) - err := Register(`provider`, flags, nil) + err := Register(`provider`, flags, nil, false) s.Error(err) s.EqualError( err, @@ -237,17 +252,68 @@ func (s *registerTestSuite) TestSuccess() { flags := pflag.NewFlagSet(`provider`, pflag.ExitOnError) flags.String(`registry.flag`, `value`, `usage`) - err := Register(`provider`, flags, nil) + err := Register(`provider`, flags, nil, false) s.NoError(err) } // --- Mocks --- +// MockFactory is an autogenerated mock type for the Factory type +type MockFactory struct { + mock.Mock +} + +// Execute provides a mock function with given fields: _a0 +func (_m *MockFactory) Execute(_a0 *viper.Viper) (core.Registry, func(), error) { + ret := _m.Called(_a0) + + var r0 core.Registry + if rf, ok := ret.Get(0).(func(*viper.Viper) core.Registry); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(core.Registry) + } + } + + var r1 func() + if rf, ok := ret.Get(1).(func(*viper.Viper) func()); ok { + r1 = rf(_a0) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(func()) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(*viper.Viper) error); ok { + r2 = rf(_a0) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + // MockProviderInterface is an autogenerated mock type for the ProviderInterface type type MockProviderInterface struct { mock.Mock } +// Deprecated provides a mock function with given fields: +func (_m *MockProviderInterface) Deprecated() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // Factory provides a mock function with given fields: func (_m *MockProviderInterface) Factory() Factory { ret := _m.Called() @@ -293,40 +359,3 @@ func (_m *MockProviderInterface) Name() string { return r0 } - -// MockFactory is an autogenerated mock type for the Factory type -type MockFactory struct { - mock.Mock -} - -// Execute provides a mock function with given fields: _a0 -func (_m *MockFactory) Execute(_a0 *viper.Viper) (core.Registry, func(), error) { - ret := _m.Called(_a0) - - var r0 core.Registry - if rf, ok := ret.Get(0).(func(*viper.Viper) core.Registry); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(core.Registry) - } - } - - var r1 func() - if rf, ok := ret.Get(1).(func(*viper.Viper) func()); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(func()) - } - } - - var r2 error - if rf, ok := ret.Get(2).(func(*viper.Viper) error); ok { - r2 = rf(_a0) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} diff --git a/internal/extension/source/extension.go b/internal/extension/source/extension.go index 5138bdc..8f4f51c 100644 --- a/internal/extension/source/extension.go +++ b/internal/extension/source/extension.go @@ -28,15 +28,17 @@ type ( Name() string Flags() *pflag.FlagSet Factory() Factory + Deprecated() bool } // ProviderList is helper custom type for handle registration and lookup for slice of ProviderInterface ProviderList []ProviderInterface provider struct { - name string - flags *pflag.FlagSet - factory Factory + name string + flags *pflag.FlagSet + factory Factory + deprecated bool } ) @@ -62,11 +64,12 @@ func MakeFlagName(name string) string { // Register new ProviderInterface implementation by name, flags and factory // Register return error if ProviderInterface already registered with same name // Register return error if ProviderInterface provide flags with incorrect name prefix -func Register(name string, flags *pflag.FlagSet, factory Factory) error { +func Register(name string, flags *pflag.FlagSet, factory Factory, deprecated bool) error { return providerList.register(&provider{ - name: name, - flags: flags, - factory: factory, + name: name, + flags: flags, + factory: factory, + deprecated: deprecated, }) } @@ -75,16 +78,21 @@ func (p *provider) Name() string { return p.name } -// Name return ProviderInterface implementation flags +// Flags return ProviderInterface implementation flags func (p *provider) Flags() *pflag.FlagSet { return p.flags } -// Name return ProviderInterface implementation factory +// Factory return ProviderInterface implementation factory func (p *provider) Factory() Factory { return p.factory } +// Deprecated return ProviderInterface implementation deprecated flag +func (p *provider) Deprecated() bool { + return p.deprecated +} + func (pl *ProviderList) register(p ProviderInterface) error { if ep, _ := pl.Lookup(p.Name()); ep != nil { return errors.Errorf(`source provider with name "%s" has been already registered`, p.Name()) diff --git a/internal/extension/source/extension_test.go b/internal/extension/source/extension_test.go index 1140ec4..9d2c980 100644 --- a/internal/extension/source/extension_test.go +++ b/internal/extension/source/extension_test.go @@ -49,6 +49,10 @@ func TestProvider_Name(t *testing.T) { suite.Run(t, new(providerNameTestSuite)) } +func TestProvider_Deprecated(t *testing.T) { + suite.Run(t, new(providerDeprecatedTestSuite)) +} + func TestRegister(t *testing.T) { suite.Run(t, new(registerTestSuite)) } @@ -199,6 +203,17 @@ func (s *providerNameTestSuite) TestSuccess() { s.Equal(`name`, p.Name()) } +type providerDeprecatedTestSuite struct { + suite.Suite +} + +func (s *providerDeprecatedTestSuite) TestSuccess() { + p := &provider{ + deprecated: false, + } + s.False(p.Deprecated()) +} + type registerTestSuite struct { suite.Suite provider *MockProviderInterface @@ -211,7 +226,7 @@ func (s *registerTestSuite) SetupTest() { func (s *registerTestSuite) TestErrorProviderHasBeenRegistered() { *providerList = append(*providerList, &provider{name: `provider`}) - err := Register(`provider`, nil, nil) + err := Register(`provider`, nil, nil, false) s.Error(err) s.EqualError(err, `source provider with name "provider" has been already registered`) } @@ -220,7 +235,7 @@ func (s *registerTestSuite) TestErrorProviderHasFlagsWithoutRequiredPrefix() { flags := pflag.NewFlagSet(`provider`, pflag.ExitOnError) flags.String(`wrong.flag`, `value`, `usage`) - err := Register(`provider`, flags, nil) + err := Register(`provider`, flags, nil, false) s.Error(err) s.EqualError( err, @@ -237,17 +252,68 @@ func (s *registerTestSuite) TestSuccess() { flags := pflag.NewFlagSet(`provider`, pflag.ExitOnError) flags.String(`source.flag`, `value`, `usage`) - err := Register(`provider`, flags, nil) + err := Register(`provider`, flags, nil, false) s.NoError(err) } // --- Mocks --- +// MockFactory is an autogenerated mock type for the Factory type +type MockFactory struct { + mock.Mock +} + +// Execute provides a mock function with given fields: _a0 +func (_m *MockFactory) Execute(_a0 *viper.Viper) (core.Source, func(), error) { + ret := _m.Called(_a0) + + var r0 core.Source + if rf, ok := ret.Get(0).(func(*viper.Viper) core.Source); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(core.Source) + } + } + + var r1 func() + if rf, ok := ret.Get(1).(func(*viper.Viper) func()); ok { + r1 = rf(_a0) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(func()) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(*viper.Viper) error); ok { + r2 = rf(_a0) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + // MockProviderInterface is an autogenerated mock type for the ProviderInterface type type MockProviderInterface struct { mock.Mock } +// Deprecated provides a mock function with given fields: +func (_m *MockProviderInterface) Deprecated() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // Factory provides a mock function with given fields: func (_m *MockProviderInterface) Factory() Factory { ret := _m.Called() @@ -293,40 +359,3 @@ func (_m *MockProviderInterface) Name() string { return r0 } - -// MockFactory is an autogenerated mock type for the Factory type -type MockFactory struct { - mock.Mock -} - -// Execute provides a mock function with given fields: _a0 -func (_m *MockFactory) Execute(_a0 *viper.Viper) (core.Source, func(), error) { - ret := _m.Called(_a0) - - var r0 core.Source - if rf, ok := ret.Get(0).(func(*viper.Viper) core.Source); ok { - r0 = rf(_a0) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(core.Source) - } - } - - var r1 func() - if rf, ok := ret.Get(1).(func(*viper.Viper) func()); ok { - r1 = rf(_a0) - } else { - if ret.Get(1) != nil { - r1 = ret.Get(1).(func()) - } - } - - var r2 error - if rf, ok := ret.Get(2).(func(*viper.Viper) error); ok { - r2 = rf(_a0) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} diff --git a/internal/extension/source/file/provider.go b/internal/extension/source/file/provider.go index 6a712b5..192f49c 100644 --- a/internal/extension/source/file/provider.go +++ b/internal/extension/source/file/provider.go @@ -20,7 +20,7 @@ func init() { set := pflag.NewFlagSet(sourceName, pflag.ExitOnError) set.String(source.MakeFlagName(flagFilePath), `$HOME/services.yml`, `YML file config path`) - if err := source.Register(sourceName, set, NewSource); err != nil { + if err := source.Register(sourceName, set, NewSource, false); err != nil { panic(err) } } diff --git a/pkg/core/manager.go b/pkg/core/manager.go index 3622876..08be93e 100644 --- a/pkg/core/manager.go +++ b/pkg/core/manager.go @@ -99,7 +99,7 @@ func (m *Manager) findOrphan(incoming Services, registered Services) Services { func (m *Manager) deregisterServices(ctx context.Context, services Services) error { me := new(managerError) for _, service := range services { - if err := m.registry.Deregister(ctx, service.RegistrationID()); err != nil { + if err := m.registry.Deregister(ctx, service); err != nil { me.Add(errors.Wrapf(err, `failed to deregister service "%s" from registry`, service.RegistrationID())) continue } diff --git a/pkg/core/manager_test.go b/pkg/core/manager_test.go index 9885085..1303877 100644 --- a/pkg/core/manager_test.go +++ b/pkg/core/manager_test.go @@ -91,7 +91,7 @@ func (s *managerRunTestSuite) TestErrorDeregisterOrphan() { sourceMock.On(`Fetch`, ctx).Return(Services{{Name: `service-1`}}, nil) registryMock := new(MockRegistry) registryMock.On(`Fetch`, ctx).Return(Services{{Name: `service-2`}}, nil) - registryMock.On(`Deregister`, ctx, `service-2`).Return(errors.New(`expected error`)) + registryMock.On(`Deregister`, ctx, &Service{Name: `service-2`}).Return(errors.New(`expected error`)) s.manager.source = sourceMock s.manager.registry = registryMock @@ -126,7 +126,7 @@ func (s *managerRunTestSuite) TestSuccess() { sourceMock.On(`Fetch`, ctx).Return(Services{{Name: `service-1`}}, nil) registryMock := new(MockRegistry) registryMock.On(`Fetch`, ctx).Return(Services{{Name: `service-2`}}, nil) - registryMock.On(`Deregister`, ctx, `service-2`).Return(nil) + registryMock.On(`Deregister`, ctx, &Service{Name: `service-2`}).Return(nil) registryMock.On(`Register`, ctx, mock.Anything).Return(nil) s.manager.source = sourceMock @@ -194,18 +194,18 @@ func (s *managerErrorHasErrorsTestSuite) TestNonEmptyError() { // --- Mocks --- -// MockRegistry is an autogenerated mock type for the Registry type +/// MockRegistry is an autogenerated mock type for the Registry type type MockRegistry struct { mock.Mock } -// Deregister provides a mock function with given fields: ctx, serviceID -func (_m *MockRegistry) Deregister(ctx context.Context, serviceID string) error { - ret := _m.Called(ctx, serviceID) +// Deregister provides a mock function with given fields: ctx, service +func (_m *MockRegistry) Deregister(ctx context.Context, service *Service) error { + ret := _m.Called(ctx, service) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, serviceID) + if rf, ok := ret.Get(0).(func(context.Context, *Service) error); ok { + r0 = rf(ctx, service) } else { r0 = ret.Error(0) } diff --git a/pkg/core/registry.go b/pkg/core/registry.go index 1c4dd45..fbee0c1 100644 --- a/pkg/core/registry.go +++ b/pkg/core/registry.go @@ -10,6 +10,6 @@ type ( Registry interface { Fetch(ctx context.Context) (Services, error) Register(ctx context.Context, service *Service) error - Deregister(ctx context.Context, serviceID string) error + Deregister(ctx context.Context, service *Service) error } ) diff --git a/pkg/core/registry/consul/registry.go b/pkg/core/registry/consul/agent/registry.go similarity index 83% rename from pkg/core/registry/consul/registry.go rename to pkg/core/registry/consul/agent/registry.go index 4691d09..da41ab2 100644 --- a/pkg/core/registry/consul/registry.go +++ b/pkg/core/registry/consul/agent/registry.go @@ -1,4 +1,4 @@ -package consul +package agent import ( "context" @@ -7,6 +7,7 @@ import ( "github.com/agrea/ptr" "github.com/hashicorp/consul/api" "github.com/insidieux/pinchy/pkg/core" + "github.com/insidieux/pinchy/pkg/core/registry/consul" "github.com/pkg/errors" "github.com/thoas/go-funk" ) @@ -19,19 +20,16 @@ type ( ServiceDeregister(serviceID string) error } - // Tag is a common tag for query and register services in registry - Tag string - // Registry is implementation of core.Registry interface Registry struct { agent Agent logger core.LoggerInterface - tag Tag + tag consul.Tag } ) // NewRegistry provide Registry as core.Registry implementation -func NewRegistry(agent Agent, tag Tag) *Registry { +func NewRegistry(agent Agent, tag consul.Tag) *Registry { return &Registry{ agent: agent, tag: tag, @@ -45,6 +43,7 @@ func (r *Registry) Fetch(_ context.Context) (core.Services, error) { if err != nil { return nil, errors.Wrap(err, `failed to fetch registered services info`) } + r.logger.Infoln(`Prepare registered services list`) result := make([]*core.Service, 0) for _, item := range registered { @@ -68,10 +67,15 @@ func (r *Registry) Fetch(_ context.Context) (core.Services, error) { } // Deregister make request for Agent.ServiceDeregister by core.Service RegistrationID -func (r *Registry) Deregister(_ context.Context, serviceID string) error { - r.logger.Infof(`Send service deregister consul agent request for service "%s"`, serviceID) - if err := r.agent.ServiceDeregister(serviceID); err != nil { - return errors.Wrapf(err, `failed deregister service by service id "%s"`, serviceID) +func (r *Registry) Deregister(ctx context.Context, service *core.Service) error { + r.logger.Infof(`Validate service "%s"`, service.RegistrationID()) + if err := service.Validate(ctx); err != nil { + return errors.Wrap(err, `service has validation error before deregister`) + } + + r.logger.Infof(`Send service deregister consul agent request for service "%s"`, service.RegistrationID()) + if err := r.agent.ServiceDeregister(service.RegistrationID()); err != nil { + return errors.Wrapf(err, `failed deregister service by service id "%s"`, service.RegistrationID()) } return nil } @@ -82,6 +86,7 @@ func (r *Registry) Register(ctx context.Context, service *core.Service) error { if err := service.Validate(ctx); err != nil { return errors.Wrap(err, `service has validation error before registration`) } + asr := &api.AgentServiceRegistration{ Kind: api.ServiceKindTypical, Name: service.Name, @@ -101,6 +106,7 @@ func (r *Registry) Register(ctx context.Context, service *core.Service) error { if service.Meta != nil { asr.Meta = *service.Meta } + r.logger.Infof(`Send service register consul agent request for service "%s"`, service.RegistrationID()) if err := r.agent.ServiceRegister(asr); err != nil { return errors.Wrapf(err, `failed register service by service id "%s"`, service.RegistrationID()) diff --git a/pkg/core/registry/consul/registry_test.go b/pkg/core/registry/consul/agent/registry_test.go similarity index 91% rename from pkg/core/registry/consul/registry_test.go rename to pkg/core/registry/consul/agent/registry_test.go index b466b37..617c20a 100644 --- a/pkg/core/registry/consul/registry_test.go +++ b/pkg/core/registry/consul/agent/registry_test.go @@ -1,4 +1,4 @@ -package consul +package agent import ( "context" @@ -41,7 +41,7 @@ type newRegistryTestSuite struct { suite.Suite } -func (s *newRegistryTestSuite) TestNewSource() { +func (s *newRegistryTestSuite) TestNewRegistry() { got := NewRegistry(nil, ``) s.Implements((*core.Registry)(nil), got) s.Equal(&Registry{nil, nil, ``}, got) @@ -102,25 +102,38 @@ type registryDeregisterTestSuite struct { suite.Suite agent *MockAgent registry *Registry + service *core.Service } func (s *registryDeregisterTestSuite) SetupTest() { s.agent = new(MockAgent) s.registry = NewRegistry(s.agent, `test`) s.registry.logger, _ = test.NewNullLogger() + s.service = &core.Service{ + Name: `service`, + Address: "127.0.0.1", + } +} + +func (s *registryDeregisterTestSuite) TestErrorServiceValidation() { + err := s.registry.Deregister(context.Background(), &core.Service{ + Name: `name`, + }) + s.Error(err) + s.Contains(err.Error(), `service has validation error before deregister`) } func (s *registryDeregisterTestSuite) TestErrorAgentDeregister() { s.agent.On(`ServiceDeregister`, `service`).Return(errors.New(`expected error`)) - err := s.registry.Deregister(context.Background(), `service`) + err := s.registry.Deregister(context.Background(), s.service) s.EqualError(err, `failed deregister service by service id "service": expected error`) } func (s *registryDeregisterTestSuite) TestSuccess() { s.agent.On(`ServiceDeregister`, `service`).Return(nil) - err := s.registry.Deregister(context.Background(), `service`) + err := s.registry.Deregister(context.Background(), s.service) s.NoError(err) } diff --git a/pkg/core/registry/consul/catalog/registry.go b/pkg/core/registry/consul/catalog/registry.go new file mode 100644 index 0000000..876cd30 --- /dev/null +++ b/pkg/core/registry/consul/catalog/registry.go @@ -0,0 +1,173 @@ +package catalog + +import ( + "context" + "fmt" + + "github.com/agrea/ptr" + "github.com/hashicorp/consul/api" + "github.com/insidieux/pinchy/pkg/core" + "github.com/insidieux/pinchy/pkg/core/registry/consul" + "github.com/pkg/errors" + "github.com/thoas/go-funk" +) + +type ( + // Catalog interface provide common function for work with Consul HTTP API /v1/catalog + Catalog interface { + Services(*api.QueryOptions) (map[string][]string, *api.QueryMeta, error) + Service(string, string, *api.QueryOptions) ([]*api.CatalogService, *api.QueryMeta, error) + Deregister(*api.CatalogDeregistration, *api.WriteOptions) (*api.WriteMeta, error) + Register(*api.CatalogRegistration, *api.WriteOptions) (*api.WriteMeta, error) + } + + // Registry is implementation of core.Registry interface + Registry struct { + catalog Catalog + logger core.LoggerInterface + tag consul.Tag + } +) + +// NewRegistry provide Registry as core.Registry implementation +func NewRegistry(catalog Catalog, tag consul.Tag) *Registry { + return &Registry{ + catalog: catalog, + tag: tag, + } +} + +// Fetch make request for Catalog.Services plus Catalog.Service and try to cast result to core.Services +func (r *Registry) Fetch(ctx context.Context) (core.Services, error) { + r.logger.Infoln(`Fetch registered services from catalog`) + query := &api.QueryOptions{ + Filter: fmt.Sprintf(`("%s" in Tags)`, r.tag), + } + query = query.WithContext(ctx) + names, _, err := r.catalog.Services(query) + if err != nil { + return nil, errors.Wrap(err, `failed to fetch registered services info`) + } + + r.logger.Infoln(`Prepare registered services list`) + result := make([]*core.Service, 0) + for name := range names { + opts := &api.QueryOptions{} + opts = opts.WithContext(ctx) + items, _, err := r.catalog.Service(name, string(r.tag), opts) + if err != nil { + return nil, errors.Wrap(err, `failed to fetch registered service info`) + } + + for _, item := range items { + service := &core.Service{ + Name: item.ServiceName, + Address: item.ServiceAddress, + ID: ptr.String(item.ServiceID), + } + if item.ServicePort != 0 { + service.Port = ptr.Int(item.ServicePort) + } + if len(item.ServiceTags) > 0 { + service.Tags = &item.ServiceTags + } + if len(item.ServiceMeta) > 0 { + service.Meta = &item.ServiceMeta + } + service.Node = &core.Node{ + Node: item.Node, + Address: item.Address, + Datacenter: ptr.String(item.Datacenter), + NodeMeta: &item.NodeMeta, + } + result = append(result, service) + } + + } + return result, nil +} + +// Deregister make request for Catalog.Deregister by core.Service RegistrationID +func (r *Registry) Deregister(ctx context.Context, service *core.Service) error { + r.logger.Infof(`Validate service "%s"`, service.RegistrationID()) + if err := service.Validate(ctx, r.validateService); err != nil { + return errors.Wrap(err, `service has validation error before deregister`) + } + + r.logger.Infof(`Send service deregister catalog request for service "%s"`, service.RegistrationID()) + opts := &api.WriteOptions{} + opts = opts.WithContext(ctx) + _, err := r.catalog.Deregister( + &api.CatalogDeregistration{ + ServiceID: service.RegistrationID(), + Node: service.Node.Node, + }, + opts, + ) + + if err != nil { + return errors.Wrapf(err, `failed deregister service by service id "%s"`, service.RegistrationID()) + } + return nil +} + +// Register make request for Catalog.Register for core.Service +func (r *Registry) Register(ctx context.Context, service *core.Service) error { + r.logger.Infof(`Validate service "%s"`, service.RegistrationID()) + if err := service.Validate(ctx, r.validateService); err != nil { + return errors.Wrap(err, `service has validation error before registration`) + } + + cr := &api.CatalogRegistration{ + Node: service.Node.Node, + Address: service.Node.Address, + Service: &api.AgentService{ + Kind: api.ServiceKindTypical, + Service: service.Name, + Address: service.Address, + Weights: api.AgentWeights{}, + EnableTagOverride: true, + }, + } + if service.ID != nil { + cr.Service.ID = *service.ID + } + if service.Port != nil { + cr.Service.Port = *service.Port + } + cr.Service.Tags = append(cr.Service.Tags, string(r.tag)) + if service.Tags != nil { + cr.Service.Tags = append(cr.Service.Tags, *service.Tags...) + } + cr.Service.Tags = funk.UniqString(cr.Service.Tags) + if service.Meta != nil { + cr.Service.Meta = *service.Meta + } + + r.logger.Infof(`Send service register catalog request for service "%s"`, service.RegistrationID()) + opts := &api.WriteOptions{} + opts = opts.WithContext(ctx) + if _, err := r.catalog.Register(cr, opts); err != nil { + return errors.Wrapf(err, `failed register service by service id "%s"`, service.RegistrationID()) + } + return nil +} + +// WithLogger is implementation of core.Loggable interface +func (r *Registry) WithLogger(logger core.LoggerInterface) { + r.logger = logger +} + +// validateService is implementation of core.ValidationFunc func +func (r *Registry) validateService(_ context.Context, service *core.Service) error { + if service.Node == nil { + return errors.New(`service field "Node" is required and cannot be empty`) + } + if service.Node.Node == `` { + return errors.New(`service field "Node.Node" is required and cannot be empty`) + } + if service.Node.Address == `` { + return errors.Errorf(`service field "Node.Address" is required and cannot be empty`) + } + return nil +} diff --git a/pkg/core/registry/consul/catalog/registry_test.go b/pkg/core/registry/consul/catalog/registry_test.go new file mode 100644 index 0000000..5a2d658 --- /dev/null +++ b/pkg/core/registry/consul/catalog/registry_test.go @@ -0,0 +1,376 @@ +package catalog + +import ( + "context" + "testing" + + "github.com/agrea/ptr" + "github.com/hashicorp/consul/api" + "github.com/insidieux/pinchy/pkg/core" + "github.com/pkg/errors" + "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +// --- Tests --- + +func TestNewRegistry(t *testing.T) { + suite.Run(t, new(newRegistryTestSuite)) +} + +func TestRegistry_Fetch(t *testing.T) { + suite.Run(t, new(registryFetchTestSuite)) +} + +func TestRegistry_Deregister(t *testing.T) { + suite.Run(t, new(registryDeregisterTestSuite)) +} + +func TestRegistry_Register(t *testing.T) { + suite.Run(t, new(registryRegisterTestSuite)) +} + +func TestRegistry_WithLogger(t *testing.T) { + suite.Run(t, new(registryWithLoggerTestSuite)) +} + +// --- Suites --- + +type newRegistryTestSuite struct { + suite.Suite +} + +func (s *newRegistryTestSuite) TestNewRegistry() { + got := NewRegistry(nil, ``) + s.Implements((*core.Registry)(nil), got) + s.Equal(&Registry{nil, nil, ``}, got) +} + +type registryFetchTestSuite struct { + suite.Suite + catalog *MockCatalog + registry *Registry +} + +func (s *registryFetchTestSuite) SetupTest() { + s.catalog = new(MockCatalog) + s.registry = NewRegistry(s.catalog, `test`) + s.registry.logger, _ = test.NewNullLogger() +} + +func (s *registryFetchTestSuite) TestErrorCatalogServicesFetch() { + s.catalog.On(`Services`, mock.Anything).Return(nil, nil, errors.New(`expected error`)) + + s.registry.catalog = s.catalog + services, err := s.registry.Fetch(context.Background()) + s.Nil(services) + s.EqualError(err, `failed to fetch registered services info: expected error`) +} + +func (s *registryFetchTestSuite) TestErrorCatalogServiceFetch() { + s.catalog.On(`Services`, mock.Anything).Return(map[string][]string{`name`: nil}, nil, nil) + s.catalog.On(`Service`, `name`, mock.Anything, mock.Anything).Return(nil, nil, errors.New(`expected error`)) + + s.registry.catalog = s.catalog + services, err := s.registry.Fetch(context.Background()) + s.Nil(services) + s.EqualError(err, `failed to fetch registered service info: expected error`) +} + +func (s *registryFetchTestSuite) TestSuccess() { + expectedTags := []string{`tags`} + expectedMeta := map[string]string{`key`: `value`} + + s.catalog.On(`Services`, mock.Anything).Return(map[string][]string{`name`: nil}, nil, nil) + s.catalog.On(`Service`, `name`, mock.Anything, mock.Anything).Return([]*api.CatalogService{ + { + ServiceName: `name`, + ServiceAddress: `127.0.0.1`, + ServiceID: `id`, + ServiceTags: expectedTags, + ServiceMeta: expectedMeta, + ServicePort: 80, + Node: `node-1`, + Datacenter: `dc-1`, + Address: `127.0.0.1`, + NodeMeta: expectedMeta, + }, + }, nil, nil) + + fetchedServices, err := s.registry.Fetch(context.Background()) + + s.NoError(err) + s.Equal(core.Services{ + &core.Service{ + Name: `name`, + Address: `127.0.0.1`, + ID: ptr.String(`id`), + Tags: &expectedTags, + Meta: &expectedMeta, + Port: ptr.Int(80), + Node: &core.Node{ + Node: `node-1`, + Address: `127.0.0.1`, + Datacenter: ptr.String(`dc-1`), + NodeMeta: &expectedMeta, + }, + }, + }, fetchedServices) + +} + +type registryDeregisterTestSuite struct { + suite.Suite + catalog *MockCatalog + registry *Registry + service *core.Service +} + +func (s *registryDeregisterTestSuite) SetupTest() { + s.catalog = new(MockCatalog) + s.registry = NewRegistry(s.catalog, `test`) + s.registry.logger, _ = test.NewNullLogger() + s.service = &core.Service{ + Name: `service`, + Address: `127.0.0.1`, + Node: &core.Node{ + Node: `node-1`, + Address: `127.0.0.1`, + }, + } +} + +func (s *registryDeregisterTestSuite) TestErrorServiceValidation() { + err := s.registry.Deregister(context.Background(), &core.Service{ + Name: s.service.Name, + }) + s.Error(err) + s.Contains(err.Error(), `service has validation error before deregister`) +} + +func (s *registryDeregisterTestSuite) TestErrorServiceCustomValidation() { + var err error + err = s.registry.Deregister(context.Background(), &core.Service{ + Name: s.service.Name, + Address: s.service.Address, + }) + s.Error(err) + s.Contains(err.Error(), `service field "Node" is required and cannot be empty`) + + err = s.registry.Deregister(context.Background(), &core.Service{ + Name: s.service.Name, + Address: s.service.Address, + Node: &core.Node{}, + }) + s.Error(err) + s.Contains(err.Error(), `service field "Node.Node" is required and cannot be empty`) + + err = s.registry.Deregister(context.Background(), &core.Service{ + Name: s.service.Name, + Address: s.service.Address, + Node: &core.Node{ + Node: s.service.Node.Node, + }, + }) + s.Error(err) + s.Contains(err.Error(), `service field "Node.Address" is required and cannot be empty`) +} + +func (s *registryDeregisterTestSuite) TestErrorCatalogDeregister() { + s.catalog.On(`Deregister`, mock.Anything, mock.Anything).Return(nil, errors.New(`expected error`)) + + err := s.registry.Deregister(context.Background(), s.service) + s.EqualError(err, `failed deregister service by service id "service": expected error`) +} + +func (s *registryDeregisterTestSuite) TestSuccess() { + s.catalog.On(`Deregister`, &api.CatalogDeregistration{ + Node: s.service.Node.Node, + ServiceID: s.service.RegistrationID(), + }, mock.Anything).Return(nil, nil) + + err := s.registry.Deregister(context.Background(), s.service) + s.NoError(err) +} + +type registryRegisterTestSuite struct { + suite.Suite + catalog *MockCatalog + registry *Registry +} + +func (s *registryRegisterTestSuite) SetupTest() { + s.catalog = new(MockCatalog) + s.registry = NewRegistry(s.catalog, `test`) + s.registry.logger, _ = test.NewNullLogger() +} + +func (s *registryRegisterTestSuite) TestErrorServiceValidation() { + err := s.registry.Register(context.Background(), &core.Service{ + Name: `name`, + }) + s.Error(err) + s.Contains(err.Error(), `service has validation error before registration`) +} + +func (s *registryRegisterTestSuite) TestErrorCatalogRegister() { + s.catalog.On(`Register`, mock.Anything, mock.Anything).Return(nil, errors.New(`expected error`)) + + err := s.registry.Register(context.Background(), &core.Service{ + Name: `name`, + Address: `127.0.0.1`, + Node: &core.Node{ + Node: `node-1`, + Address: `127.0.0.1`, + }, + }) + s.EqualError(err, `failed register service by service id "name": expected error`) +} + +func (s *registryRegisterTestSuite) TestSuccess() { + s.catalog.On(`Register`, mock.Anything, mock.Anything).Return(nil, nil) + + expectedTags := []string{`tags`} + expectedMeta := map[string]string{`key`: `value`} + err := s.registry.Register(context.Background(), &core.Service{ + Name: `name`, + Address: `127.0.0.1`, + ID: ptr.String(`id`), + Tags: &expectedTags, + Meta: &expectedMeta, + Port: ptr.Int(80), + Node: &core.Node{ + Node: `node-1`, + Address: `127.0.0.1`, + }, + }) + s.NoError(err) +} + +type registryWithLoggerTestSuite struct { + suite.Suite +} + +func (s *registryWithLoggerTestSuite) TestWithLogger() { + logger, _ := test.NewNullLogger() + src := NewRegistry(nil, ``) + src.WithLogger(logger) +} + +// --- Mocks --- + +// MockCatalog is an autogenerated mock type for the Catalog type +type MockCatalog struct { + mock.Mock +} + +// Deregister provides a mock function with given fields: _a0, _a1 +func (_m *MockCatalog) Deregister(_a0 *api.CatalogDeregistration, _a1 *api.WriteOptions) (*api.WriteMeta, error) { + ret := _m.Called(_a0, _a1) + + var r0 *api.WriteMeta + if rf, ok := ret.Get(0).(func(*api.CatalogDeregistration, *api.WriteOptions) *api.WriteMeta); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*api.WriteMeta) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*api.CatalogDeregistration, *api.WriteOptions) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Register provides a mock function with given fields: _a0, _a1 +func (_m *MockCatalog) Register(_a0 *api.CatalogRegistration, _a1 *api.WriteOptions) (*api.WriteMeta, error) { + ret := _m.Called(_a0, _a1) + + var r0 *api.WriteMeta + if rf, ok := ret.Get(0).(func(*api.CatalogRegistration, *api.WriteOptions) *api.WriteMeta); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*api.WriteMeta) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*api.CatalogRegistration, *api.WriteOptions) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Service provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockCatalog) Service(_a0 string, _a1 string, _a2 *api.QueryOptions) ([]*api.CatalogService, *api.QueryMeta, error) { + ret := _m.Called(_a0, _a1, _a2) + + var r0 []*api.CatalogService + if rf, ok := ret.Get(0).(func(string, string, *api.QueryOptions) []*api.CatalogService); ok { + r0 = rf(_a0, _a1, _a2) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*api.CatalogService) + } + } + + var r1 *api.QueryMeta + if rf, ok := ret.Get(1).(func(string, string, *api.QueryOptions) *api.QueryMeta); ok { + r1 = rf(_a0, _a1, _a2) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*api.QueryMeta) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(string, string, *api.QueryOptions) error); ok { + r2 = rf(_a0, _a1, _a2) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Services provides a mock function with given fields: _a0 +func (_m *MockCatalog) Services(_a0 *api.QueryOptions) (map[string][]string, *api.QueryMeta, error) { + ret := _m.Called(_a0) + + var r0 map[string][]string + if rf, ok := ret.Get(0).(func(*api.QueryOptions) map[string][]string); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string][]string) + } + } + + var r1 *api.QueryMeta + if rf, ok := ret.Get(1).(func(*api.QueryOptions) *api.QueryMeta); ok { + r1 = rf(_a0) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*api.QueryMeta) + } + } + + var r2 error + if rf, ok := ret.Get(2).(func(*api.QueryOptions) error); ok { + r2 = rf(_a0) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} diff --git a/pkg/core/registry/consul/types.go b/pkg/core/registry/consul/types.go new file mode 100644 index 0000000..289b47a --- /dev/null +++ b/pkg/core/registry/consul/types.go @@ -0,0 +1,6 @@ +package consul + +type ( + // Tag is a common tag for query and register services in registry + Tag string +) diff --git a/pkg/core/service.go b/pkg/core/service.go index 7a3a7e4..97610f9 100644 --- a/pkg/core/service.go +++ b/pkg/core/service.go @@ -9,6 +9,14 @@ import ( ) type ( + // Node contains info about host/node/server, hosting service. Used for catalog registration in consul. + Node struct { + Node string `json:","` + Address string `json:","` + Datacenter *string `json:",omitempty"` + NodeMeta *map[string]string `json:",omitempty"` + } + // Service contains all the necessary information for further registration in Registry Service struct { Name string `json:","` @@ -17,20 +25,30 @@ type ( Port *int `json:",omitempty"` Tags *[]string `json:",omitempty"` Meta *map[string]string `json:",omitempty"` + Node *Node `json:",omitempty"` } // Services is simple helper for hold slice of Service's Services []*Service + + // ValidationFunc is a func providing additional validation, called by Source or Registry + ValidationFunc func(context.Context, *Service) error ) // Validate process validation to check required fields for Service, such as Service.Name and Service.Address -func (s *Service) Validate(_ context.Context) error { +// Also there is a possibility to pass your own additional checks +func (s *Service) Validate(ctx context.Context, checks ...ValidationFunc) error { if s.Name == `` { return errors.New(`service field "name" is required and cannot be empty`) } if s.Address == `` { return errors.Errorf(`service "%s" field "address" is required and cannot be empty`, s.Name) } + for _, check := range checks { + if err := check(ctx, s); err != nil { + return errors.Wrapf(err, `service "%s" custom check failed`, s.Name) + } + } return nil } diff --git a/pkg/core/service_test.go b/pkg/core/service_test.go index 698ad6d..c147d3c 100644 --- a/pkg/core/service_test.go +++ b/pkg/core/service_test.go @@ -2,6 +2,7 @@ package core import ( "context" + "errors" "testing" "github.com/agrea/ptr" @@ -47,6 +48,15 @@ func (s *serviceValidateTestSuite) TestEmptyAddress() { s.Error(s.service.Validate(context.Background())) } +func (s *serviceValidateTestSuite) TestCustomValidation() { + s.service.Name = `service` + s.service.Address = `127.0.0.1` + s.service.Port = ptr.Int(80) + s.Error(s.service.Validate(context.Background(), func(ctx context.Context, service *Service) error { + return errors.New(`expected error`) + })) +} + func (s *serviceValidateTestSuite) TestValidationPassed() { s.service.Name = `service` s.service.Address = `127.0.0.1` diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/test/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore