Compare commits

..

106 Commits

Author SHA1 Message Date
chenzhihui 4289e1da63 fix param order 2 years ago
chenzhihui e4daa56e53 Merge remote-tracking branch 'origin' into status-code-override 2 years ago
chenzhihui 239360516a add Redirector to the request with a redirect to url 2 years ago
dependabot[bot] 02ac2944ca build(deps): bump gopkg.in/yaml.v3 in /contrib/config/nacos (#2067) 2 years ago
dependabot[bot] f5c46e2c12 build(deps): bump gopkg.in/yaml.v3 in /contrib/config/apollo (#2068) 2 years ago
Tony Chen f7da448a88 fix with logger (#2062) 2 years ago
dependabot[bot] 402532a6cd build(deps): bump k8s.io/client-go in /contrib/config/kubernetes (#2056) 2 years ago
dependabot[bot] 8fead618c0 build(deps): bump k8s.io/client-go in /contrib/registry/kubernetes (#2057) 2 years ago
dependabot[bot] dc060e7afd build(deps): bump github.com/aliyun/aliyun-log-go-sdk (#2053) 2 years ago
dependabot[bot] 17600f8fb2 build(deps): bump k8s.io/api in /contrib/config/kubernetes (#2052) 2 years ago
Tony Chen 2dd247be8f log: remove component logger to use global logger (#2061) 2 years ago
dependabot[bot] ebf78ce69f build(deps): bump k8s.io/api in /contrib/registry/kubernetes (#2058) 2 years ago
包子 d2129bb114 fix: config log 2 years ago
包子 e388547d82 feat: add config log (#2060) 2 years ago
Tony Chen 00b71747e8 app: fix instance nil when not registered (#2059) 2 years ago
dependabot[bot] ff474e6a70 build(deps): bump actions/setup-go from 2 to 3.2.0 (#2048) 2 years ago
dependabot[bot] 418916d010 build(deps): bump golangci/golangci-lint-action from 2 to 3 (#2047) 2 years ago
dependabot[bot] ac5b5124c1 build(deps): bump actions/checkout from 2 to 3 (#2046) 2 years ago
dependabot[bot] 155ef482d0 build(deps): bump github/codeql-action from 1 to 2 (#2045) 2 years ago
包子 55237aab3e Update dependabot.yml 2 years ago
包子 c65bf30b04 Create dependabot.yml 2 years ago
longxboy 57a935c8d1 feat:add context for log (#2041) 2 years ago
包子 ded51d352c deps: upgrade kratos version to v2.3.0 (#2036) 2 years ago
Tony Chen f8e4b620f2 opensergo: fix http path & method (#2035) 2 years ago
Kagaya 850710a356 feat(contrib/opensergo): add opensergo reportMetadata fields (#1996) 2 years ago
Tony Chen c4b3b5a978 log: fix global logger (#2034) 2 years ago
Tony Chen bc25b3d81b http: handle default mux (#2033) 2 years ago
longxboy 16db9c889a fix:fix error camel case (#2032) 2 years ago
haiyux 31bffc2a7f deps: upgrade go mod version (#2028) 2 years ago
songzhibin97 d57a3c2a8b Update project.go (#2024) 2 years ago
songzhibin97 bee818891d Update server.go (#2021) 2 years ago
songzhibin97 901307e51f style:change (#2022) 2 years ago
songzhibin97 9eaee21a03 Update path.go (#2023) 2 years ago
songzhibin97 cda3eaadcd Update run.go (#2025) 2 years ago
songzhibin97 7815e482f3 stype:gen_go_errors (#2026) 2 years ago
songzhibin97 4e51b25f11 Update server.go (#2027) 2 years ago
songzhibin97 3a15f5df9e style:ewma (#2018) 2 years ago
songzhibin97 72ba4e5c43 Style:trace (#2017) 2 years ago
songzhibin97 9abcff9d30 Update jwt.go (#2016) 2 years ago
songzhibin97 a5a4fd2e0d Update metadata.go (#2015) 2 years ago
songzhibin97 996a1d0956 style:log (#2014) 2 years ago
songzhibin97 8755827c33 Update encode.go (#2019) 2 years ago
Ccheers 93ac681455 feat(registry): zookeeper watch node changed (#1986) 2 years ago
songzhibin97 fa3d9c3fc1 Update endpoint.go (#2012) 2 years ago
songzhibin97 291f0f3ad0 Update host.go (#2013) 2 years ago
songzhibin97 769a40957c Update errors.go (#2009) 2 years ago
songzhibin97 40b7a6b069 fix:#2006 config.atomicValue Other basic types are supported (#2007) 2 years ago
songzhibin97 d8118045f4 Update file.go (#2005) 2 years ago
qi 8a8694c16c fix(protoc-gen-go-errors): fix generated function comments have extra blank lines. (#2008) 2 years ago
songzhibin97 3375816ce0 fix:#2002 definition service lowercase generation cannot be exported (#2003) 2 years ago
JinChang d6e3fed0cf Added ability to configure protojson (#1993) 2 years ago
包子 57380fa021 fix: update ci gocyclo min-complexity 50 (#2000) 2 years ago
pwli ec69624f8a fix:(transport/http): fix unexpected overriding behavior when return an error (#1984) 2 years ago
houseme 784d96058e fix: Replace the docker image of Polaris (#1999) 2 years ago
songzhibin97 bcf98570fe fix:#1987 Http and grpc generate route alignment (#1988) 2 years ago
Comolli a2765e27b4 feat: decode google.protobuf.BytesValue compatible base64.URLEncoding. (#1991) 2 years ago
Kagaya 0a8ab7a48a docs: remove redundant info & fix typo in pr template (#1997) 2 years ago
Kagaya c329cffa3f ignore polaris test & add eureka consul test (#1998) 2 years ago
qi ced122a688 fix: query params: parse list failed, unsupported message type: "google.protobuf.Struct" #1967 (#1989) 2 years ago
Nikita Krasnikov be9cfde7a4 Fix buildMethodDesc for two and more message field (#1979) 2 years ago
witt 49a9572118 fix: delete redundant type conversion (#1977) 2 years ago
Jinming Guo bdcd9c4525 fix: add 'go mod tidy' cmd to make quick-start available (#1973) 2 years ago
Underworld511 f1b8ede77f Update README_zh.md (#1975) 2 years ago
包子 a61cd5a6ee deps: upgrade kratos version to v2.2.2 (#1944) 2 years ago
Elric Li b44daec0db feat(log): Helper implemented io.Writer (#1927) 2 years ago
JeffreyBool 48df7b1b71 feat: protoc-gen-go-errors add comment (#1961) 2 years ago
林晓炜 42640e92e1 feat(registry): contrib/registry/zookeeper add digest acl support (#1964) 2 years ago
张东龙 662e8c7b17 fix(transport/http): responseEncoder should not write any data when it need to write nil (#1945) 2 years ago
haiyux 9a98fcb04f feat:new and add karge warehouse (#1953) 2 years ago
haiyux eaa5b161b4 fix: fix kratos run when cmd number is one (#1956) 2 years ago
haiyux 572270ba91 fix: fix choose failed when paths cmd/server (#1954) 2 years ago
Casper-Mars 2d5da155fd fix(contrib/opensergo): fix index error (#1951) 2 years ago
张东龙 65e5d680b1 fix bind test errors (#1950) 2 years ago
李洛克 11273fc9a6 fix client do method done not use when err not nil (#1948) 2 years ago
Tony Chen 045c5001c9 feat: add opensergo metadata (#1947) 2 years ago
yeqown 00e38a6c2b feat(discovery): provide an option to disable discovery debug log (#1942) 2 years ago
Tony Chen 0dc30dde72 feat(selector): add node scheme (#1932) 2 years ago
Windfarer 13e5fd26af correct the func name in meatadata log (#1915) 2 years ago
Xudong Cai acb4588d69 fix: error case2Camel (#1923) 2 years ago
Tony Chen a9ebd0822c feat: add error cause for statck trace (#1910) 2 years ago
Casper-Mars 4133bc497c feat: change description of Kratos (#1920) 2 years ago
Casper-Mars 64c4417de3 feat(registry): consul client add DeregisterCriticalServiceAfter option (#1917) 2 years ago
Casper-Mars d0dd3309db fix(cmd/protoc-gen-go-errors):fix lint problem (#1919) 2 years ago
yoogo 53d5795def fix(cmd): protoc-gen-go-http use self release (#1909) 2 years ago
Tony Chen 58bce8dd11 add json codec for grpc (#1908) 2 years ago
包子 a58dd0c110 fix(transport): fix the problem that the context is not delivered correctly (#1906) 2 years ago
包子 1873cea771 fix(log/aliyun): Improper Input Validation in GoGo Protobuf CVE-2021-3121 (#1902) 2 years ago
Cluas a589fde695 fix(log): FilterFunc keyvals lost logger prefix (#1901) 2 years ago
Cluas 8ba2190aa8 feat(log): log load config only when Debug level (#1899) 2 years ago
weetime db93345733 feat(contrib): add eureka registry (#1792) (#1793) 2 years ago
Tony Chen c764d65454 fix: starter parent ctx (#1895) 2 years ago
longxboy 48d88ac6ab feat: supprt non-kratos instance in consul registry (#1892) 2 years ago
包子 a2702db493 deps: upgrade kratos version to 2.2.1 (#1890) 2 years ago
包子 f69bcf0f50 fix: update semantic (#1891) 2 years ago
包子 b80835a131 fix(cmd): proto-gen-go-http warn only when ending with a slash (#1887) 2 years ago
Tony Chen 1bb7e377e5 fix(log): call depth (#1885) 2 years ago
letian 22a3a9e9bb fix: kratos command error on windows (#1884) 2 years ago
Tony Chen 29824954fa fix: decode empty body (#1882) 2 years ago
Sasha Melentyev caada1acc7 ci: Add 1.18 (#1880) 2 years ago
hoslo 92357bc162 fix: graceful shutdown (#1873) 2 years ago
包子 13d3da62ac chore: remove examples (#1871) 2 years ago
包子 3e39b139bd feat: add semantic.yml (#1876) 2 years ago
kwanhur e49a3e6801 style(cmd/errors/examples/middleware/transport): fix common words' spelling mistakes (#1872) 2 years ago
Chen Xinyuan 4fcff315b2 fix: fix some typos (#1869) 2 years ago
kwanhur c3de800938 chore(examples): optimize typos of swagger/server (#1862) 2 years ago
Casper 6dc826b7ea feat(transport/http):add StatusCoder to support overriding http status code 3 years ago
  1. 2
      .github/ISSUE_TEMPLATE/feature-request.md
  2. 13
      .github/pull_request_template.md
  3. 12
      .github/stable.yml
  4. 26
      .github/workflows/go.yml
  5. 16
      .github/workflows/issue-translator.yml
  6. 5
      .gitignore
  7. 3
      .golangci.yml
  8. 8
      CONTRIBUTING.md
  9. 31
      Makefile
  10. 27
      README.md
  11. 24
      README_zh.md
  12. 2
      SECURITY.md
  13. 168
      api/metadata/metadata.pb.go
  14. 6
      api/metadata/metadata_grpc.pb.go
  15. 2
      api/metadata/metadata_http.pb.go
  16. 29
      api/metadata/server.go
  17. 47
      app.go
  18. 101
      app_test.go
  19. 10
      cmd/kratos/go.mod
  20. 38
      cmd/kratos/go.sum
  21. 34
      cmd/kratos/internal/base/mod_test.go
  22. 23
      cmd/kratos/internal/base/path.go
  23. 45
      cmd/kratos/internal/base/repo.go
  24. 50
      cmd/kratos/internal/base/repo_test.go
  25. 58
      cmd/kratos/internal/base/vcs_url.go
  26. 55
      cmd/kratos/internal/base/vcs_url_test.go
  27. 2
      cmd/kratos/internal/change/change.go
  28. 9
      cmd/kratos/internal/change/get.go
  29. 25
      cmd/kratos/internal/change/get_test.go
  30. 13
      cmd/kratos/internal/project/add.go
  31. 16
      cmd/kratos/internal/project/new.go
  32. 73
      cmd/kratos/internal/project/project.go
  33. 29
      cmd/kratos/internal/project/project_linux_test.go
  34. 144
      cmd/kratos/internal/project/project_test.go
  35. 30
      cmd/kratos/internal/project/project_windows_test.go
  36. 19
      cmd/kratos/internal/proto/add/add.go
  37. 38
      cmd/kratos/internal/proto/add/add_test.go
  38. 6
      cmd/kratos/internal/proto/add/proto.go
  39. 6
      cmd/kratos/internal/proto/client/client.go
  40. 8
      cmd/kratos/internal/proto/proto.go
  41. 37
      cmd/kratos/internal/proto/server/server.go
  42. 102
      cmd/kratos/internal/proto/server/server_test.go
  43. 4
      cmd/kratos/internal/proto/server/template.go
  44. 31
      cmd/kratos/internal/run/run.go
  45. 6
      cmd/kratos/internal/upgrade/upgrade.go
  46. 4
      cmd/kratos/main.go
  47. 2
      cmd/kratos/version.go
  48. 5
      cmd/protoc-gen-go-errors/errors.go
  49. 17
      cmd/protoc-gen-go-errors/errorsTemplate.tpl
  50. 2
      cmd/protoc-gen-go-errors/go.mod
  51. 24
      cmd/protoc-gen-go-errors/go.sum
  52. 22
      cmd/protoc-gen-go-errors/template.go
  53. 2
      cmd/protoc-gen-go-errors/version.go
  54. 72
      cmd/protoc-gen-go-http/http.go
  55. 93
      cmd/protoc-gen-go-http/httpTemplate.tpl
  56. 3
      cmd/protoc-gen-go-http/main.go
  57. 91
      cmd/protoc-gen-go-http/template.go
  58. 2
      cmd/protoc-gen-go-http/version.go
  59. 8
      config/config.go
  60. 41
      config/config_test.go
  61. 16
      config/env/env.go
  62. 26
      config/env/env_test.go
  63. 4
      config/env/watcher.go
  64. 16
      config/file/file_test.go
  65. 43
      config/file/format_test.go
  66. 5
      config/file/watcher.go
  67. 2
      config/options.go
  68. 43
      config/options_test.go
  69. 20
      config/reader.go
  70. 167
      config/reader_test.go
  71. 54
      config/value.go
  72. 75
      config/value_test.go
  73. 1
      contrib/config/apollo/README.md
  74. 186
      contrib/config/apollo/apollo.go
  75. 153
      contrib/config/apollo/apollo_test.go
  76. 9
      contrib/config/apollo/go.mod
  77. 992
      contrib/config/apollo/go.sum
  78. 34
      contrib/config/apollo/parser.go
  79. 44
      contrib/config/apollo/watcher.go
  80. 9
      contrib/config/consul/README.md
  81. 7
      contrib/config/consul/config.go
  82. 158
      contrib/config/consul/config_test.go
  83. 9
      contrib/config/consul/go.mod
  84. 1583
      contrib/config/consul/go.sum
  85. 27
      contrib/config/consul/watcher.go
  86. 17
      contrib/config/etcd/README.md
  87. 3
      contrib/config/etcd/config.go
  88. 61
      contrib/config/etcd/config_test.go
  89. 6
      contrib/config/etcd/go.mod
  90. 1460
      contrib/config/etcd/go.sum
  91. 31
      contrib/config/etcd/watcher.go
  92. 3
      contrib/config/kubernetes/config.go
  93. 170
      contrib/config/kubernetes/config_test.go
  94. 8
      contrib/config/kubernetes/go.mod
  95. 1229
      contrib/config/kubernetes/go.sum
  96. 3
      contrib/config/kubernetes/watcher.go
  97. 3
      contrib/config/nacos/README.md
  98. 97
      contrib/config/nacos/config.go
  99. 223
      contrib/config/nacos/config_test.go
  100. 3
      contrib/config/nacos/go.mod
  101. Some files were not shown because too many files have changed in this diff Show More

@ -43,7 +43,7 @@ example:
### Requirements description of the feature
<!--
example:
The event interface should be added to Kratos. The interface should contain subscribers and publishers, and the message body should contain key value head
The event interface should be added to Kratos. The interface should contain subscribers and publishers, and the message body should contain key value heade
-->
### References
<!--

@ -3,21 +3,10 @@
1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/
2. Ensure you have added or ran the appropriate tests and lint for your PR, please use `make lint` and `make test` before filing your PR, use `make clean` to tidy your go mod.
3. If the PR is unfinished, you may need to mark it as a WIP(Work In Progress) PR or Draft PR
3. If the PR is unfinished, you may need to mark it as a WIP(Work In Progress) PR or draft PR
4. Please use a semantic commits format title, such as `<type>[optional scope]: <description>`, see: https://go-kratos.dev/docs/community/contribution#type
5. at the same time, please note that similar work should be submitted in one PR as far as possible to reduce the workload of reviewers. Do not split a work into multiple PR unless it should.
-->
<!--
🎉 感谢您向 Kratos 发送 PR!以下是一些提示:
如果这是你第一次为 Kratos 贡献,请阅读我们的贡献指南:https://go-kratos.dev/en/docs/community/contribution/
2、确保您已经为您的 PR 添加或运行了适当的测试和lint,请在提交PR之前使用“make lint”和“make test”,使用“make clean”整理您的 go.mod。
3、如果 PR 未完成,您可能需要将其标记为 WIP(Work In Progress)PR 或 Draft PR
4、请使用语义提交格式标题,如“<类型>[可选范围]:<说明>`,请参阅:https://go-kratos.dev/docs/community/contribution#type
5. 同时请注意,同类的工作请尽量在一个PR中提交,以减轻 review 者的工作负担,不要把一项工作拆分成很多个PR,除非它应该这样做。
-->
#### Description (what this PR does / why we need it):
<!--
* The description should include the motivation for this PR or contrast this with previous behavior

@ -1,12 +0,0 @@
daysUntilStale: 30
daysUntilClose: 3
exemptLabels:
- pinned
- security
- bug
staleLabel: wontfix
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
closeComment: true

@ -13,7 +13,7 @@ jobs:
build:
strategy:
matrix:
go: [1.19,1.20.x]
go: [1.16, 1.17, 1.18]
name: build & test
runs-on: ubuntu-latest
services:
@ -25,44 +25,28 @@ jobs:
ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379
consul:
image: consul:1.12.3
image: consul:latest
ports:
- 8500:8500
nacos:
image: nacos/nacos-server:v2.1.0
image: nacos/nacos-server:latest
env:
MODE: standalone
ports:
- "8848:8848"
- "9848:9848"
polaris:
image: polarismesh/polaris-standalone:latest
image: houseme/polaris-server-with-config:latest
ports:
- 8090:8090
- 8091:8091
- 8093:8093
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4.0.1
uses: actions/setup-go@v3.2.0
with:
go-version: ${{ matrix.go }}
- name: Setup Environment
run: |
echo "GOPATH=$(go env GOPATH)" >> $GITHUB_ENV
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
- name: Module cache
uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go
- name: Build
run: go build ./...

@ -1,16 +0,0 @@
name: 'issue-translator'
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: true
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑🤝🧑👫🧑🏿🤝🧑🏻👩🏾🤝👨🏿👬🏿
BOT_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}

5
.gitignore vendored

@ -3,6 +3,7 @@
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
@ -14,10 +15,6 @@
# Dependency directories (remove the comment below to include it)
vendor/
# Go workspace file
go.work
go.work.sum
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a

@ -31,7 +31,6 @@ linters:
- varcheck
- whitespace
- wastedassign
- unconvert
# don't enable:
# - asciicheck
@ -67,5 +66,3 @@ linters-settings:
gocyclo:
# recommend 10-20
min-complexity: 50
goimports:
local-prefixes: github.com/go-kratos # Put imports beginning with prefix after 3rd-party packages

@ -1,7 +1,7 @@
The kratos community wants to be helped by a wide range of developers, so you'd like to take a few minutes to read this guide before you mention the problem or pull request.
## Reporting Bug or Fixing Bugs
We use GitHub issues to manage issues. If you want to submit , first make sure you've searched for existing issues, pull requests and read our [FAQ](https://go-kratos.dev/docs/intro/faq).
## Reportings Bug or Fixing Bugs
We use github issues to manage issues. If you want to submit , first make sure you've searched for existing issues, pull requests and read our [FAQ](https://go-kratos.dev/docs/intro/faq).
When submitting a bug report, use the issue template we provide to clearly describe the problems encountered and how to reproduce, and if convenient it is best to provide a minimal reproduce repository.
@ -16,9 +16,9 @@ After the function is implemented, a merge request will be initiated to associat
After the merge is completed, Close all issues.
## How to submit code
If you've never submitted code on GitHub, follow these steps:
If you've never submitted code on github, follow these steps:
- First, please fork items to your GitHub account
- First, please fork items to your github account
- Then create a new feature branch based on the main branch and name it features such as feature-log
- Write code
- Submit code to the far end branch

@ -1,27 +1,9 @@
user := $(shell whoami)
rev := $(shell git rev-parse --short HEAD)
os := $(shell expr substr $(shell uname -s) 1 5)
# GOBIN > GOPATH > INSTALLDIR
# Mac OS X
ifeq ($(shell uname),Darwin)
GOBIN := $(shell echo ${GOBIN} | cut -d':' -f1)
GOPATH := $(shell echo $(GOPATH) | cut -d':' -f1)
endif
# Linux
ifeq ($(os),Linux)
GOBIN := $(shell echo ${GOBIN} | cut -d':' -f1)
GOPATH := $(shell echo $(GOPATH) | cut -d':' -f1)
endif
# Windows
ifeq ($(os),MINGW)
GOBIN := $(subst \,/,$(GOBIN))
GOPATH := $(subst \,/,$(GOPATH))
GOBIN :=/$(shell echo "$(GOBIN)" | cut -d';' -f1 | sed 's/://g')
GOPATH :=/$(shell echo "$(GOPATH)" | cut -d';' -f1 | sed 's/://g')
endif
BIN := ""
TOOLS_SHELL="./hack/tools.sh"
@ -32,7 +14,7 @@ LINTER := bin/golangci-lint
ifneq ($(GOBIN),)
BIN=$(GOBIN)
else
# check GOPATH
# check GOPATH
ifneq ($(GOPATH),)
BIN=$(GOPATH)/bin
endif
@ -55,9 +37,9 @@ ifeq ($(user),root)
@cp ./cmd/protoc-gen-go-http/protoc-gen-go-http /usr/bin
else
#!root, install for current user
$(shell if [ -z '$(BIN)' ]; then read -p "Please select installdir: " REPLY; mkdir -p $${REPLY};\
cp ./cmd/kratos/kratos $${REPLY}/;cp ./cmd/protoc-gen-go-errors/protoc-gen-go-errors $${REPLY}/;cp ./cmd/protoc-gen-go-http/protoc-gen-go-http $${REPLY}/;else mkdir -p '$(BIN)';\
cp ./cmd/kratos/kratos '$(BIN)';cp ./cmd/protoc-gen-go-errors/protoc-gen-go-errors '$(BIN)';cp ./cmd/protoc-gen-go-http/protoc-gen-go-http '$(BIN)'; fi)
$(shell if [ -z $(BIN) ]; then read -p "Please select installdir: " REPLY; mkdir -p $${REPLY};\
cp ./cmd/kratos/kratos $${REPLY}/;cp ./cmd/protoc-gen-go-errors/protoc-gen-go-errors $${REPLY}/;cp ./cmd/protoc-gen-go-http/protoc-gen-go-http $${REPLY}/;else mkdir -p $(BIN);\
cp ./cmd/kratos/kratos $(BIN);cp ./cmd/protoc-gen-go-errors/protoc-gen-go-errors $(BIN);cp ./cmd/protoc-gen-go-http/protoc-gen-go-http $(BIN); fi)
endif
@which protoc-gen-go &> /dev/null || go get google.golang.org/protobuf/cmd/protoc-gen-go
@which protoc-gen-go-grpc &> /dev/null || go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
@ -95,8 +77,3 @@ test-coverage:
lint: $(LINTER)
@${TOOLS_SHELL} lint
@echo "lint check finished"
.PHONY: proto
proto:
protoc --proto_path=./api --proto_path=./third_party --go_out=paths=source_relative:./api --go-grpc_out=paths=source_relative:./api --go-http_out=paths=source_relative:./api metadata/metadata.proto
protoc --proto_path=./third_party --go_out=paths=source_relative:./errors/errors.proto

@ -17,21 +17,21 @@
## About Kratos
> The name is inspired by the Greek-mythology-based game "God of War". It tells the adventures of Kratos becoming a god of war from a mortal and launching a god-killing slaughter.
> The name is inspired by the game God of War which is based on Greek myths, tells the adventures of Kratos who became a god of war from a mortal and launched a god-killing slaughter.
Kratos is a microservice-oriented governance framework implemented by golang, which offers convenient capabilities to help you quickly build a bulletproof application from scratch, such as:
- The [communication protocol](https://go-kratos.dev/en/docs/component/api) is based on the HTTP/gRPC through the definition of Protobuf.
- Abstract [transport](https://go-kratos.dev/en/docs/component/transport/overview) layer support: [HTTP](https://go-kratos.dev/en/docs/component/transport/http) / [gRPC](https://go-kratos.dev/en/docs/component/transport/grpc).
- Powerful [middleware](https://go-kratos.dev/en/docs/component/middleware/overview) design, support: [Tracing (OpenTelemetry)](https://go-kratos.dev/en/docs/component/middleware/tracing), [Metrics (Prometheus is default)](https://go-kratos.dev/en/docs/component/middleware/metrics), [Recovery](https://go-kratos.dev/en/docs/component/middleware/recovery) and more.
- Abstract [transport](https://go-kratos.dev/en/docs/component/transport/overview) layer are support: [HTTP](https://go-kratos.dev/en/docs/component/transport/http) / [gRPC](https://go-kratos.dev/en/docs/component/transport/grpc).
- Powerful [middleware](https://go-kratos.dev/en/docs/component/middleware/overview) design, support: [Tracing (OpenTelemetry)](https://go-kratos.dev/en/docs/component/middleware/tracing)、[Metrics (Prometheus is default)](https://go-kratos.dev/en/docs/component/middleware/metrics)、[Recovery](https://go-kratos.dev/en/docs/component/middleware/recovery) and more.
- [Registry](https://go-kratos.dev/en/docs/component/registry) interface able to be connected with various other centralized registries through plug-ins.
- The [standard log interfaces](https://go-kratos.dev/en/docs/component/log) ease the integration of the third-party log libs with logs collected through the *Fluentd*.
- Automatically support the selection of the content [encoding](https://go-kratos.dev/en/docs/component/encoding) with Accept and Content-Type.
- The [standard log interfaces](https://go-kratos.dev/en/docs/component/log) ease the integration of the third-party log libs and logs are collected through the *Fluentd*.
- The selection of the content [encoding](https://go-kratos.dev/en/docs/component/encoding) is automatically supported by Accept and Content-Type.
- Multiple data sources are supported for [configurations](https://go-kratos.dev/en/docs/component/config) and dynamic configurations (use atomic operations).
- In the protocol of HTTP/gRPC, use the uniform [metadata](https://go-kratos.dev/en/docs/component/metadata) transfer method.
- In the protocol of HTTP/gRPC, use uniform [metadata](https://go-kratos.dev/en/docs/component/metadata) transfer method.
- You can define [errors](https://go-kratos.dev/en/docs/component/errors/) in protos and generate enums with protoc-gen-go.
- You can define [verification rules](https://go-kratos.dev/en/docs/component/middleware/validate) in Protobuf supported by the HTTP/gRPC service.
- [Swagger API](https://go-kratos.dev/en/docs/guide/openapi) is generated Automatically and embed Swagger UI endpoint can be started by adding [Swagger plugin](https://github.com/go-kratos/swagger-api).
- You can define [verification rules](https://go-kratos.dev/en/docs/component/middleware/validate) in Protobuf which is supported by HTTP/gRPC service.
- [Swagger API](https://go-kratos.dev/en/docs/guide/openapi) generated Automatically and embed Swagger UI endpoint can be started by adding [Swagger plugin](https://github.com/go-kratos/swagger-api).
Kratos is accessible, powerful, and provides tools required for large, robust applications.
@ -39,7 +39,7 @@ Kratos is accessible, powerful, and provides tools required for large, robust ap
Kratos has the most extensive and thorough [documentation](https://go-kratos.dev/en/docs/getting-started/start) and [example](https://github.com/go-kratos/examples) library of all modern web application frameworks, making it a breeze to get started with the framework.
We also provide a [modern template](https://github.com/go-kratos/kratos-layout). This template should help reduce the work required to setup up modern projects.
We also provide a [modern template](https://github.com/go-kratos/kratos-layout), This template should help reduce the work required to setup up a modern project.
### Goals
@ -47,13 +47,13 @@ Kratos boosts your productivity. With the integration of excellent resources and
### Principles
* **Simple**: Appropriate design with plain and easy code.
* **Simple**: Appropriate design, plain and easy code.
* **General**: Cover the various utilities for business development.
* **Highly efficient**: Speeding up the efficiency of businesses upgrading.
* **Stable**: The base libs validated in the production environment have the characteristics of high testability, high coverage as well as high security and reliability.
* **Stable**: The base libs validated in the production environment which have the characters of the high testability, high coverage as well as high security and reliability.
* **Robust**: Eliminating misusing through high quality of the base libs.
* **High-performance**: Optimal performance excluding the optimization of hacking in case of *unsafe*. 
* **Expandability**: Properly designed interfaces where you can expand utilities such as base libs to meet your further requirements.
* **Expandability**: Properly designed interfaces, you can expand utilities such as base libs to meet your further requirements.
* **Fault-tolerance**: Designed against failure, enhance the understanding and exercising of SRE within Kratos to achieve more robustness.
* **Toolchain**: Includes an extensive toolchain, such as the code generation of cache, the lint tool, and so forth.
@ -90,6 +90,7 @@ If you discover a security vulnerability within Kratos, please send an e-mail to
- [Wechat Group](https://github.com/go-kratos/kratos/issues/682)
- [Discord Group](https://discord.gg/BWzJsUJ)
- [go-kratos.dev](https://go-kratos.dev/en)
- QQ Group: 716486124
## Contributors
@ -110,5 +111,5 @@ The following project had particular influence on kratos's design.
- [go-kit/kit](https://github.com/go-kit/kit) is a programming toolkit for building microservices in go.
- [asim/go-micro](https://github.com/asim/go-micro) a distributed systems development framework.
- [google/go-cloud](https://github.com/google/go-cloud) is go cloud development kit.
- [zeromicro/go-zero](https://github.com/zeromicro/go-zero) is a web and rpc framework with lots of builtin engineering practices.
- [tal-tech/go-zero](https://github.com/tal-tech/go-zero) is a web and rpc framework with lots of builtin engineering practices.
- [beego/beego](https://github.com/beego/beego) is a web framework including RESTful APIs, web apps and backend services.

@ -38,18 +38,18 @@ Kratos 一套轻量级 Go 微服务框架,包含大量微服务相关功能及
* 工具链:包含大量工具链,比如 cache 代码生成,lint 工具等等;
## Features
* [APIs](https://go-kratos.dev/docs/component/api) :协议通信以 HTTP/gRPC 为基础,通过 Protobuf 进行定义;
* [Errors](https://go-kratos.dev/docs/component/errors/) :通过 Protobuf 的 Enum 作为错误码定义,以及工具生成判定接口;
* [Metadata](https://go-kratos.dev/docs/component/metadata) :在协议通信 HTTP/gRPC 中,通过 Middleware 规范化服务元信息传递;
* [Config](https://go-kratos.dev/docs/component/config) :支持多数据源方式,进行配置合并铺平,通过 Atomic 方式支持动态配置;
* [Logger](https://go-kratos.dev/docs/component/log) :标准日志接口,可方便集成三方 log 库,并可通过 fluentd 收集日志;
* [Metrics](https://go-kratos.dev/docs/component/middleware/metrics) :统一指标接口,可以实现各种指标系统,默认集成 Prometheus;
* [Tracing](https://go-kratos.dev/docs/component/middleware/tracing) :遵循 OpenTelemetry 规范定义,以实现微服务链路追踪;
* [Encoding](https://go-kratos.dev/docs/component/encoding) :支持 Accept 和 Content-Type 进行自动选择内容编码;
* [Transport](https://go-kratos.dev/docs/component/transport/overview) :通用的 [HTTP](https://go-kratos.dev/docs/component/transport/http) /[gRPC](https://go-kratos.dev/docs/component/transport/grpc) 传输层,实现统一的 [Middleware](https://go-kratos.dev/docs/component/middleware/overview) 插件支持;
* [Registry](https://go-kratos.dev/docs/component/registry) :实现统一注册中心接口,可插件化对接各种注册中心;
* [Validation](https://go-kratos.dev/docs/component/middleware/validate): 通过Protobuf统一定义校验规则,并同时适用于HTTP/gRPC服务.
* [SwaggerAPI](https://go-kratos.dev/docs/guide/openapi): 通过集成第三方[Swagger插件](https://github.com/go-kratos/swagger-api) 能够自动生成Swagger API json并启动一个内置的Swagger UI服务.
* [APIs](https://github.com/go-kratos/examples/helloworld/helloworld) :协议通信以 HTTP/gRPC 为基础,通过 Protobuf 进行定义;
* [Errors](https://github.com/go-kratos/examples/errors/api) :通过 Protobuf 的 Enum 作为错误码定义,以及工具生成判定接口;
* [Metadata](https://github.com/go-kratos/examples/metadata) :在协议通信 HTTP/gRPC 中,通过 Middleware 规范化服务元信息传递;
* [Config](https://github.com/go-kratos/examples/config) :支持多数据源方式,进行配置合并铺平,通过 Atomic 方式支持动态配置;
* [Logger](https://github.com/go-kratos/examples/log) :标准日志接口,可方便集成三方 log 库,并可通过 fluentd 收集日志;
* [Metrics](https://github.com/go-kratos/examples/metrics) :统一指标接口,可以实现各种指标系统,默认集成 Prometheus;
* [Tracing](https://github.com/go-kratos/examples/traces) :遵循 OpenTelemetry 规范定义,以实现微服务链路追踪;
* [Encoding](encoding):支持 Accept 和 Content-Type 进行自动选择内容编码;
* [Transport](transport/transport.go) :通用的 [HTTP](https://github.com/go-kratos/examples/http/middlewares) /[gRPC](https://github.com/go-kratos/examples/middleware/main.go) 传输层,实现统一的 [Middleware](middleware) 插件支持;
* [Registry](https://github.com/go-kratos/examples/registry) :实现统一注册中心接口,可插件化对接各种注册中心;
* [Validation](https://github.com/go-kratos/examples/validate): 通过Protobuf统一定义校验规则,并同时适用于HTTP/gRPC服务.
* [SwaggerAPI](https://github.com/go-kratos/swagger-api/blob/main/examples/helloworld/server/main.go): 通过集成第三方[Swagger插件](https://github.com/go-kratos/swagger-api) 能够自动生成Swagger API json并启动一个内置的Swagger UI服务.
## Getting Started
### Required

@ -6,7 +6,7 @@ Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
|-------------|--------------------|
| ------- | ------------------ |
| 2.0.rc1 | :white_check_mark: |
| < 2.0.beta3 | :x: |

@ -1,8 +1,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v3.19.4
// source: metadata/metadata.proto
// protoc-gen-go v1.26.0
// protoc v3.17.3
// source: metadata.proto
package metadata
@ -31,7 +31,7 @@ type ListServicesRequest struct {
func (x *ListServicesRequest) Reset() {
*x = ListServicesRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_metadata_metadata_proto_msgTypes[0]
mi := &file_metadata_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -44,7 +44,7 @@ func (x *ListServicesRequest) String() string {
func (*ListServicesRequest) ProtoMessage() {}
func (x *ListServicesRequest) ProtoReflect() protoreflect.Message {
mi := &file_metadata_metadata_proto_msgTypes[0]
mi := &file_metadata_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -57,7 +57,7 @@ func (x *ListServicesRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead.
func (*ListServicesRequest) Descriptor() ([]byte, []int) {
return file_metadata_metadata_proto_rawDescGZIP(), []int{0}
return file_metadata_proto_rawDescGZIP(), []int{0}
}
type ListServicesReply struct {
@ -72,7 +72,7 @@ type ListServicesReply struct {
func (x *ListServicesReply) Reset() {
*x = ListServicesReply{}
if protoimpl.UnsafeEnabled {
mi := &file_metadata_metadata_proto_msgTypes[1]
mi := &file_metadata_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -85,7 +85,7 @@ func (x *ListServicesReply) String() string {
func (*ListServicesReply) ProtoMessage() {}
func (x *ListServicesReply) ProtoReflect() protoreflect.Message {
mi := &file_metadata_metadata_proto_msgTypes[1]
mi := &file_metadata_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -98,7 +98,7 @@ func (x *ListServicesReply) ProtoReflect() protoreflect.Message {
// Deprecated: Use ListServicesReply.ProtoReflect.Descriptor instead.
func (*ListServicesReply) Descriptor() ([]byte, []int) {
return file_metadata_metadata_proto_rawDescGZIP(), []int{1}
return file_metadata_proto_rawDescGZIP(), []int{1}
}
func (x *ListServicesReply) GetServices() []string {
@ -126,7 +126,7 @@ type GetServiceDescRequest struct {
func (x *GetServiceDescRequest) Reset() {
*x = GetServiceDescRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_metadata_metadata_proto_msgTypes[2]
mi := &file_metadata_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -139,7 +139,7 @@ func (x *GetServiceDescRequest) String() string {
func (*GetServiceDescRequest) ProtoMessage() {}
func (x *GetServiceDescRequest) ProtoReflect() protoreflect.Message {
mi := &file_metadata_metadata_proto_msgTypes[2]
mi := &file_metadata_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -152,7 +152,7 @@ func (x *GetServiceDescRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetServiceDescRequest.ProtoReflect.Descriptor instead.
func (*GetServiceDescRequest) Descriptor() ([]byte, []int) {
return file_metadata_metadata_proto_rawDescGZIP(), []int{2}
return file_metadata_proto_rawDescGZIP(), []int{2}
}
func (x *GetServiceDescRequest) GetName() string {
@ -173,7 +173,7 @@ type GetServiceDescReply struct {
func (x *GetServiceDescReply) Reset() {
*x = GetServiceDescReply{}
if protoimpl.UnsafeEnabled {
mi := &file_metadata_metadata_proto_msgTypes[3]
mi := &file_metadata_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -186,7 +186,7 @@ func (x *GetServiceDescReply) String() string {
func (*GetServiceDescReply) ProtoMessage() {}
func (x *GetServiceDescReply) ProtoReflect() protoreflect.Message {
mi := &file_metadata_metadata_proto_msgTypes[3]
mi := &file_metadata_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -199,7 +199,7 @@ func (x *GetServiceDescReply) ProtoReflect() protoreflect.Message {
// Deprecated: Use GetServiceDescReply.ProtoReflect.Descriptor instead.
func (*GetServiceDescReply) Descriptor() ([]byte, []int) {
return file_metadata_metadata_proto_rawDescGZIP(), []int{3}
return file_metadata_proto_rawDescGZIP(), []int{3}
}
func (x *GetServiceDescReply) GetFileDescSet() *descriptorpb.FileDescriptorSet {
@ -209,74 +209,74 @@ func (x *GetServiceDescReply) GetFileDescSet() *descriptorpb.FileDescriptorSet {
return nil
}
var File_metadata_metadata_proto protoreflect.FileDescriptor
var file_metadata_metadata_proto_rawDesc = []byte{
0x0a, 0x17, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x6b, 0x72, 0x61, 0x74, 0x6f,
0x73, 0x2e, 0x61, 0x70, 0x69, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f,
0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x49, 0x0a, 0x11,
0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c,
0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20,
0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a,
0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0x2b, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x22, 0x5d, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x46, 0x0a, 0x0d, 0x66,
0x69, 0x6c, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01,
0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
0x74, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x52, 0x0b, 0x66, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63,
0x53, 0x65, 0x74, 0x32, 0xdd, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
0x12, 0x61, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
0x12, 0x1f, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69,
0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1d, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x11, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12, 0x09, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x73, 0x12, 0x6e, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x44, 0x65, 0x73, 0x63, 0x12, 0x21, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61,
0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73,
0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f,
0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x44, 0x65, 0x73, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02,
0x12, 0x12, 0x10, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x7b, 0x6e, 0x61,
0x6d, 0x65, 0x7d, 0x42, 0x63, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x50, 0x01, 0x5a, 0x3c,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72,
0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x61,
0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f,
0x61, 0x70, 0x69, 0x3b, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xa2, 0x02, 0x09, 0x4b,
0x72, 0x61, 0x74, 0x6f, 0x73, 0x41, 0x50, 0x49, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
var File_metadata_proto protoreflect.FileDescriptor
var file_metadata_proto_rawDesc = []byte{
0x0a, 0x0e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x0a, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x1a, 0x20, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x15, 0x0a, 0x13,
0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x22, 0x49, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x72, 0x76,
0x69, 0x63, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18,
0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0x2b,
0x0a, 0x15, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63,
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x5d, 0x0a, 0x13, 0x47,
0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x52, 0x65, 0x70,
0x6c, 0x79, 0x12, 0x46, 0x0a, 0x0d, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x5f,
0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65,
0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x52, 0x0b, 0x66,
0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x53, 0x65, 0x74, 0x32, 0xdd, 0x01, 0x0a, 0x08, 0x4d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x61, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53,
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73,
0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f,
0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
0x65, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x11, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0b, 0x12,
0x09, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x6e, 0x0a, 0x0e, 0x47, 0x65,
0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x12, 0x21, 0x2e, 0x6b,
0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72,
0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x1f, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74,
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x52, 0x65, 0x70, 0x6c, 0x79,
0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69,
0x63, 0x65, 0x73, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x42, 0x63, 0x0a, 0x15, 0x63, 0x6f,
0x6d, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e,
0x61, 0x70, 0x69, 0x50, 0x01, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, 0x74,
0x6f, 0x73, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x3b, 0x6d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0xa2, 0x02, 0x09, 0x4b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x41, 0x50, 0x49, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_metadata_metadata_proto_rawDescOnce sync.Once
file_metadata_metadata_proto_rawDescData = file_metadata_metadata_proto_rawDesc
file_metadata_proto_rawDescOnce sync.Once
file_metadata_proto_rawDescData = file_metadata_proto_rawDesc
)
func file_metadata_metadata_proto_rawDescGZIP() []byte {
file_metadata_metadata_proto_rawDescOnce.Do(func() {
file_metadata_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(file_metadata_metadata_proto_rawDescData)
func file_metadata_proto_rawDescGZIP() []byte {
file_metadata_proto_rawDescOnce.Do(func() {
file_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(file_metadata_proto_rawDescData)
})
return file_metadata_metadata_proto_rawDescData
return file_metadata_proto_rawDescData
}
var file_metadata_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_metadata_metadata_proto_goTypes = []interface{}{
var file_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_metadata_proto_goTypes = []interface{}{
(*ListServicesRequest)(nil), // 0: kratos.api.ListServicesRequest
(*ListServicesReply)(nil), // 1: kratos.api.ListServicesReply
(*GetServiceDescRequest)(nil), // 2: kratos.api.GetServiceDescRequest
(*GetServiceDescReply)(nil), // 3: kratos.api.GetServiceDescReply
(*descriptorpb.FileDescriptorSet)(nil), // 4: google.protobuf.FileDescriptorSet
}
var file_metadata_metadata_proto_depIdxs = []int32{
var file_metadata_proto_depIdxs = []int32{
4, // 0: kratos.api.GetServiceDescReply.file_desc_set:type_name -> google.protobuf.FileDescriptorSet
0, // 1: kratos.api.Metadata.ListServices:input_type -> kratos.api.ListServicesRequest
2, // 2: kratos.api.Metadata.GetServiceDesc:input_type -> kratos.api.GetServiceDescRequest
@ -289,13 +289,13 @@ var file_metadata_metadata_proto_depIdxs = []int32{
0, // [0:1] is the sub-list for field type_name
}
func init() { file_metadata_metadata_proto_init() }
func file_metadata_metadata_proto_init() {
if File_metadata_metadata_proto != nil {
func init() { file_metadata_proto_init() }
func file_metadata_proto_init() {
if File_metadata_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_metadata_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
file_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListServicesRequest); i {
case 0:
return &v.state
@ -307,7 +307,7 @@ func file_metadata_metadata_proto_init() {
return nil
}
}
file_metadata_metadata_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
file_metadata_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ListServicesReply); i {
case 0:
return &v.state
@ -319,7 +319,7 @@ func file_metadata_metadata_proto_init() {
return nil
}
}
file_metadata_metadata_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
file_metadata_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetServiceDescRequest); i {
case 0:
return &v.state
@ -331,7 +331,7 @@ func file_metadata_metadata_proto_init() {
return nil
}
}
file_metadata_metadata_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
file_metadata_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetServiceDescReply); i {
case 0:
return &v.state
@ -348,18 +348,18 @@ func file_metadata_metadata_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_metadata_metadata_proto_rawDesc,
RawDescriptor: file_metadata_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_metadata_metadata_proto_goTypes,
DependencyIndexes: file_metadata_metadata_proto_depIdxs,
MessageInfos: file_metadata_metadata_proto_msgTypes,
GoTypes: file_metadata_proto_goTypes,
DependencyIndexes: file_metadata_proto_depIdxs,
MessageInfos: file_metadata_proto_msgTypes,
}.Build()
File_metadata_metadata_proto = out.File
file_metadata_metadata_proto_rawDesc = nil
file_metadata_metadata_proto_goTypes = nil
file_metadata_metadata_proto_depIdxs = nil
File_metadata_proto = out.File
file_metadata_proto_rawDesc = nil
file_metadata_proto_goTypes = nil
file_metadata_proto_depIdxs = nil
}

@ -1,8 +1,4 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.19.4
// source: metadata/metadata.proto
package metadata
@ -141,5 +137,5 @@ var Metadata_ServiceDesc = grpc.ServiceDesc{
},
},
Streams: []grpc.StreamDesc{},
Metadata: "metadata/metadata.proto",
Metadata: "metadata.proto",
}

@ -1,6 +1,6 @@
// Code generated by protoc-gen-go-http. DO NOT EDIT.
// versions:
// protoc-gen-go-http v2.3.0
// protoc-gen-go-http v2.0.0
package metadata

@ -4,10 +4,8 @@ import (
"bytes"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"sort"
"sync"
"google.golang.org/grpc"
@ -15,13 +13,14 @@ import (
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
dpb "google.golang.org/protobuf/types/descriptorpb"
"github.com/go-kratos/kratos/v2/log"
)
//nolint:lll
//go:generate protoc --proto_path=. --proto_path=../../third_party --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. --go-http_out=paths=source_relative:. metadata.proto
// Server is api meta server
type Server struct {
UnimplementedMetadataServer
@ -76,10 +75,6 @@ func (s *Server) load() error {
}
fdps, e := allDependency(fdp)
if e != nil {
if errors.Is(e, protoregistry.NotFound) {
// Skip this service if one of its dependencies is not found.
continue
}
err = e
return false
}
@ -98,16 +93,13 @@ func (s *Server) load() error {
}
// ListServices return all services
func (s *Server) ListServices(_ context.Context, _ *ListServicesRequest) (*ListServicesReply, error) {
func (s *Server) ListServices(ctx context.Context, in *ListServicesRequest) (*ListServicesReply, error) {
s.lock.Lock()
defer s.lock.Unlock()
if err := s.load(); err != nil {
return nil, err
}
reply := &ListServicesReply{
Services: make([]string, 0, len(s.services)),
Methods: make([]string, 0, len(s.methods)),
}
reply := new(ListServicesReply)
for name := range s.services {
reply.Services = append(reply.Services, name)
}
@ -116,13 +108,11 @@ func (s *Server) ListServices(_ context.Context, _ *ListServicesRequest) (*ListS
reply.Methods = append(reply.Methods, fmt.Sprintf("/%s/%s", name, method))
}
}
sort.Strings(reply.Services)
sort.Strings(reply.Methods)
return reply, nil
}
// GetServiceDesc return service meta by name
func (s *Server) GetServiceDesc(_ context.Context, in *GetServiceDescRequest) (*GetServiceDescReply, error) {
func (s *Server) GetServiceDesc(ctx context.Context, in *GetServiceDescRequest) (*GetServiceDescReply, error) {
s.lock.Lock()
defer s.lock.Unlock()
if err := s.load(); err != nil {
@ -170,8 +160,7 @@ func allDependency(fd *dpb.FileDescriptorProto) ([]*dpb.FileDescriptorProto, err
for _, dep := range fd.Dependency {
fdDep, err := fileDescriptorProto(dep)
if err != nil {
log.Warnf("%s", err)
continue
return nil, err
}
temp, err := allDependency(fdDep)
if err != nil {
@ -199,7 +188,7 @@ func decompress(b []byte) ([]byte, error) {
func fileDescriptorProto(path string) (*dpb.FileDescriptorProto, error) {
fd, err := protoregistry.GlobalFiles.FindFileByPath(path)
if err != nil {
return nil, fmt.Errorf("find proto by path failed, path: %s, err: %s", path, err)
return nil, err
}
fdpb := protodesc.ToFileDescriptorProto(fd)
return fdpb, nil

@ -89,15 +89,8 @@ func (a *App) Run() error {
a.mu.Lock()
a.instance = instance
a.mu.Unlock()
sctx := NewContext(a.ctx, a)
eg, ctx := errgroup.WithContext(sctx)
eg, ctx := errgroup.WithContext(NewContext(a.ctx, a))
wg := sync.WaitGroup{}
for _, fn := range a.opts.beforeStart {
if err = fn(sctx); err != nil {
return err
}
}
for _, srv := range a.opts.servers {
srv := srv
eg.Go(func() error {
@ -108,64 +101,54 @@ func (a *App) Run() error {
})
wg.Add(1)
eg.Go(func() error {
wg.Done() // here is to ensure server start has begun running before register, so defer is not needed
return srv.Start(sctx)
wg.Done()
return srv.Start(NewContext(a.opts.ctx, a))
})
}
wg.Wait()
if a.opts.registrar != nil {
rctx, rcancel := context.WithTimeout(ctx, a.opts.registrarTimeout)
defer rcancel()
if err = a.opts.registrar.Register(rctx, instance); err != nil {
return err
}
}
for _, fn := range a.opts.afterStart {
if err = fn(sctx); err != nil {
if err := a.opts.registrar.Register(rctx, instance); err != nil {
return err
}
}
c := make(chan os.Signal, 1)
signal.Notify(c, a.opts.sigs...)
eg.Go(func() error {
for {
select {
case <-ctx.Done():
return nil
return ctx.Err()
case <-c:
return a.Stop()
}
})
if err = eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
if err := a.Stop(); err != nil {
return err
}
for _, fn := range a.opts.afterStop {
err = fn(sctx)
}
}
})
if err := eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
return err
}
return nil
}
// Stop gracefully stops the application.
func (a *App) Stop() (err error) {
sctx := NewContext(a.ctx, a)
for _, fn := range a.opts.beforeStop {
err = fn(sctx)
}
func (a *App) Stop() error {
a.mu.Lock()
instance := a.instance
a.mu.Unlock()
if a.opts.registrar != nil && instance != nil {
ctx, cancel := context.WithTimeout(NewContext(a.ctx, a), a.opts.registrarTimeout)
defer cancel()
if err = a.opts.registrar.Deregister(ctx, instance); err != nil {
if err := a.opts.registrar.Deregister(ctx, instance); err != nil {
return err
}
}
if a.cancel != nil {
a.cancel()
}
return err
return nil
}
func (a *App) buildInstance() (*registry.ServiceInstance, error) {

@ -2,8 +2,7 @@ package kratos
import (
"context"
"errors"
"net/url"
"fmt"
"reflect"
"sync"
"testing"
@ -19,9 +18,9 @@ type mockRegistry struct {
service map[string]*registry.ServiceInstance
}
func (r *mockRegistry) Register(_ context.Context, service *registry.ServiceInstance) error {
func (r *mockRegistry) Register(ctx context.Context, service *registry.ServiceInstance) error {
if service == nil || service.ID == "" {
return errors.New("no service id")
return fmt.Errorf("no service id")
}
r.lk.Lock()
defer r.lk.Unlock()
@ -30,11 +29,11 @@ func (r *mockRegistry) Register(_ context.Context, service *registry.ServiceInst
}
// Deregister the registration.
func (r *mockRegistry) Deregister(_ context.Context, service *registry.ServiceInstance) error {
func (r *mockRegistry) Deregister(ctx context.Context, service *registry.ServiceInstance) error {
r.lk.Lock()
defer r.lk.Unlock()
if r.service[service.ID] == nil {
return errors.New("deregister service not found")
return fmt.Errorf("deregister service not found")
}
delete(r.service, service.ID)
return nil
@ -47,22 +46,6 @@ func TestApp(t *testing.T) {
Name("kratos"),
Version("v1.0.0"),
Server(hs, gs),
BeforeStart(func(_ context.Context) error {
t.Log("BeforeStart...")
return nil
}),
BeforeStop(func(_ context.Context) error {
t.Log("BeforeStop...")
return nil
}),
AfterStart(func(_ context.Context) error {
t.Log("AfterStart...")
return nil
}),
AfterStop(func(_ context.Context) error {
t.Log("AfterStop...")
return nil
}),
Registrar(&mockRegistry{service: make(map[string]*registry.ServiceInstance)}),
)
time.AfterFunc(time.Second, func() {
@ -109,80 +92,6 @@ func TestApp_Metadata(t *testing.T) {
}
func TestApp_Endpoint(t *testing.T) {
v := []string{"https://go-kratos.dev", "localhost"}
var endpoints []*url.URL
for _, urlStr := range v {
if endpoint, err := url.Parse(urlStr); err != nil {
t.Errorf("invalid endpoint:%v", urlStr)
} else {
endpoints = append(endpoints, endpoint)
}
}
o := New(Endpoint(endpoints...))
if instance, err := o.buildInstance(); err != nil {
t.Error("build instance failed")
} else {
o.instance = instance
}
if !reflect.DeepEqual(o.Endpoint(), v) {
t.Errorf("Endpoint() = %v, want %v", o.Endpoint(), v)
}
}
func TestApp_buildInstance(t *testing.T) {
want := struct {
id string
name string
version string
metadata map[string]string
endpoints []string
}{
id: "1",
name: "kratos",
version: "v1.0.0",
metadata: map[string]string{
"a": "1",
"b": "2",
},
endpoints: []string{"https://go-kratos.dev", "localhost"},
}
var endpoints []*url.URL
for _, urlStr := range want.endpoints {
if endpoint, err := url.Parse(urlStr); err != nil {
t.Errorf("invalid endpoint:%v", urlStr)
} else {
endpoints = append(endpoints, endpoint)
}
}
app := New(
ID(want.id),
Name(want.name),
Version(want.version),
Metadata(want.metadata),
Endpoint(endpoints...),
)
if got, err := app.buildInstance(); err != nil {
t.Error("build got failed")
} else {
if got.ID != want.id {
t.Errorf("ID() = %v, want %v", got.ID, want.id)
}
if got.Name != want.name {
t.Errorf("Name() = %v, want %v", got.Name, want.name)
}
if got.Version != want.version {
t.Errorf("Version() = %v, want %v", got.Version, want.version)
}
if !reflect.DeepEqual(got.Endpoints, want.endpoints) {
t.Errorf("Endpoint() = %v, want %v", got.Endpoints, want.endpoints)
}
if !reflect.DeepEqual(got.Metadata, want.metadata) {
t.Errorf("Metadata() = %v, want %v", got.Metadata, want.metadata)
}
}
}
func TestApp_Context(t *testing.T) {
type fields struct {
id string
version string

@ -3,13 +3,15 @@ module github.com/go-kratos/kratos/cmd/kratos/v2
go 1.16
require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/AlecAivazis/survey/v2 v2.3.4
github.com/emicklei/proto v1.10.0
github.com/fatih/color v1.13.0
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/spf13/cobra v1.4.0
github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
golang.org/x/text v0.4.0
gopkg.in/yaml.v3 v3.0.0 // indirect
golang.org/x/mod v0.5.1
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
golang.org/x/text v0.3.7
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

@ -1,5 +1,5 @@
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/AlecAivazis/survey/v2 v2.3.4 h1:pchTU9rsLUSvWEl2Aq9Pv3k0IE2fkqtGxazskAMd9Ng=
github.com/AlecAivazis/survey/v2 v2.3.4/go.mod h1:hrV6Y/kQCLhIZXGcriDCUBtB3wnN7156gMXJ3+b23xM=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@ -39,41 +39,37 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
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/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/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=

@ -1,9 +1,6 @@
package base
import (
"os"
"testing"
)
import "testing"
func TestModuleVersion(t *testing.T) {
v, err := ModuleVersion("golang.org/x/mod")
@ -12,32 +9,3 @@ func TestModuleVersion(t *testing.T) {
}
t.Log(v)
}
func TestModulePath(t *testing.T) {
if err := os.Mkdir("/tmp/test_mod", os.ModePerm); err != nil {
t.Fatal(err)
}
f, err := os.Create("/tmp/test_mod/go.mod")
if err != nil {
t.Fatal(err)
}
mod := `module github.com/go-kratos/kratos/v2
go 1.16`
_, err = f.WriteString(mod)
if err != nil {
t.Fatal(err)
}
p, err := ModulePath("/tmp/test_mod/go.mod")
if err != nil {
t.Fatal(err)
}
if p != "github.com/go-kratos/kratos/v2" {
t.Fatalf("want: %s, got: %s", "github.com/go-kratos/kratos/v2", p)
}
t.Cleanup(func() { os.RemoveAll("/tmp/test_mod") })
}

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"strings"
@ -16,7 +17,7 @@ func kratosHome() string {
if err != nil {
log.Fatal(err)
}
home := filepath.Join(dir, ".kratos")
home := path.Join(dir, ".kratos")
if _, err := os.Stat(home); os.IsNotExist(err) {
if err := os.MkdirAll(home, 0o700); err != nil {
log.Fatal(err)
@ -26,7 +27,7 @@ func kratosHome() string {
}
func kratosHomeWithDir(dir string) string {
home := filepath.Join(kratosHome(), dir)
home := path.Join(kratosHome(), dir)
if _, err := os.Stat(home); os.IsNotExist(err) {
if err := os.MkdirAll(home, 0o700); err != nil {
log.Fatal(err)
@ -36,6 +37,7 @@ func kratosHomeWithDir(dir string) string {
}
func copyFile(src, dst string, replaces []string) error {
var err error
srcinfo, err := os.Stat(src)
if err != nil {
return err
@ -56,26 +58,27 @@ func copyFile(src, dst string, replaces []string) error {
}
func copyDir(src, dst string, replaces, ignores []string) error {
srcinfo, err := os.Stat(src)
if err != nil {
var err error
var fds []os.DirEntry
var srcinfo os.FileInfo
if srcinfo, err = os.Stat(src); err != nil {
return err
}
err = os.MkdirAll(dst, srcinfo.Mode())
if err != nil {
if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
return err
}
fds, err := os.ReadDir(src)
if err != nil {
if fds, err = os.ReadDir(src); err != nil {
return err
}
for _, fd := range fds {
if hasSets(fd.Name(), ignores) {
continue
}
srcfp := filepath.Join(src, fd.Name())
dstfp := filepath.Join(dst, fd.Name())
srcfp := path.Join(src, fd.Name())
dstfp := path.Join(dst, fd.Name())
var e error
if fd.IsDir() {
e = copyDir(srcfp, dstfp, replaces, ignores)

@ -3,16 +3,13 @@ package base
import (
"context"
"fmt"
"net"
stdurl "net/url"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
)
var unExpandVarPath = []string{"~", ".", ".."}
// Repo is git repository manager.
type Repo struct {
url string
@ -21,21 +18,27 @@ type Repo struct {
}
func repoDir(url string) string {
vcsURL, err := ParseVCSUrl(url)
if err != nil {
return url
if !strings.Contains(url, "//") {
url = "//" + url
}
// check host contains port
host, _, err := net.SplitHostPort(vcsURL.Host)
if err != nil {
host = vcsURL.Host
if strings.HasPrefix(url, "//git@") {
url = "ssh:" + url
} else if strings.HasPrefix(url, "//") {
url = "https:" + url
}
for _, p := range unExpandVarPath {
host = strings.TrimLeft(host, p)
u, err := stdurl.Parse(url)
if err == nil {
url = fmt.Sprintf("%s://%s%s", u.Scheme, u.Hostname(), u.Path)
}
dir := path.Base(path.Dir(vcsURL.Path))
url = fmt.Sprintf("%s/%s", host, dir)
return url
var start int
start = strings.Index(url, "//")
if start == -1 {
start = strings.Index(url, ":") + 1
} else {
start += 2
}
end := strings.LastIndex(url, "/")
return url[start:end]
}
// NewRepo new a repository manager.
@ -69,15 +72,15 @@ func (r *Repo) Pull(ctx context.Context) error {
cmd.Dir = r.Path()
_, err := cmd.CombinedOutput()
if err != nil {
return err
return nil
}
cmd = exec.CommandContext(ctx, "git", "pull")
cmd.Dir = r.Path()
out, err := cmd.CombinedOutput()
fmt.Println(string(out))
if err != nil {
return err
}
fmt.Println(string(out))
return err
}
@ -93,10 +96,10 @@ func (r *Repo) Clone(ctx context.Context) error {
cmd = exec.CommandContext(ctx, "git", "clone", "-b", r.branch, r.url, r.Path())
}
out, err := cmd.CombinedOutput()
fmt.Println(string(out))
if err != nil {
return err
}
fmt.Println(string(out))
return nil
}
@ -105,7 +108,7 @@ func (r *Repo) CopyTo(ctx context.Context, to string, modPath string, ignores []
if err := r.Clone(ctx); err != nil {
return err
}
mod, err := ModulePath(filepath.Join(r.Path(), "go.mod"))
mod, err := ModulePath(path.Join(r.Path(), "go.mod"))
if err != nil {
return err
}
@ -117,7 +120,7 @@ func (r *Repo) CopyToV2(ctx context.Context, to string, modPath string, ignores,
if err := r.Clone(ctx); err != nil {
return err
}
mod, err := ModulePath(filepath.Join(r.Path(), "go.mod"))
mod, err := ModulePath(path.Join(r.Path(), "go.mod"))
if err != nil {
return err
}

@ -7,37 +7,7 @@ import (
)
func TestRepo(t *testing.T) {
urls := []string{
// ssh://[user@]host.xz[:port]/path/to/repo.git/
"ssh://git@github.com:7875/go-kratos/kratos.git",
// git://host.xz[:port]/path/to/repo.git/
"git://github.com:7875/go-kratos/kratos.git",
// http[s]://host.xz[:port]/path/to/repo.git/
"https://github.com:7875/go-kratos/kratos.git",
// ftp[s]://host.xz[:port]/path/to/repo.git/
"ftps://github.com:7875/go-kratos/kratos.git",
//[user@]host.xz:path/to/repo.git/
"git@github.com:go-kratos/kratos.git",
// ssh://[user@]host.xz[:port]/~[user]/path/to/repo.git/
"ssh://git@github.com:7875/go-kratos/kratos.git",
// git://host.xz[:port]/~[user]/path/to/repo.git/
"git://github.com:7875/go-kratos/kratos.git",
//[user@]host.xz:/~[user]/path/to/repo.git/
"git@github.com:go-kratos/kratos.git",
///path/to/repo.git/
"//github.com/go-kratos/kratos.git",
// file:///path/to/repo.git/
"file://./github.com/go-kratos/kratos.git",
}
for _, url := range urls {
dir := repoDir(url)
if dir != "github.com/go-kratos" && dir != "/go-kratos" {
t.Fatal(url, "repoDir test failed", dir)
}
}
}
func TestRepoClone(t *testing.T) {
os.RemoveAll("/tmp/test_repo")
r := NewRepo("https://github.com/go-kratos/service-layout.git", "")
if err := r.Clone(context.Background()); err != nil {
t.Fatal(err)
@ -45,7 +15,19 @@ func TestRepoClone(t *testing.T) {
if err := r.CopyTo(context.Background(), "/tmp/test_repo", "github.com/go-kratos/kratos-layout", nil); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
os.RemoveAll("/tmp/test_repo")
})
urls := []string{
"ssh://git@gitlab.xxx.com:1234/foo/bar.git",
"ssh://gitlab.xxx.com:1234/foo/bar.git",
"//git@gitlab.xxx.com:1234/foo/bar.git",
"git@gitlab.xxx.com:1234/foo/bar.git",
"gitlab.xxx.com:1234/foo/bar.git",
"gitlab.xxx.com/foo/bar.git",
"gitlab.xxx.com/foo/bar",
}
for _, url := range urls {
dir := repoDir(url)
if dir != "gitlab.xxx.com/foo" {
t.Fatal("repoDir test failed", dir)
}
}
}

@ -1,58 +0,0 @@
package base
import (
"errors"
"net/url"
"regexp"
"strings"
)
var (
scpSyntaxRe = regexp.MustCompile(`^(\w+)@([\w.-]+):(.*)$`)
scheme = []string{"git", "https", "http", "git+ssh", "ssh", "file", "ftp", "ftps"}
)
// ParseVCSUrl ref https://github.com/golang/go/blob/master/src/cmd/go/internal/vcs/vcs.go
// see https://go-review.googlesource.com/c/go/+/12226/
// git url define https://git-scm.com/docs/git-clone#_git_urls
func ParseVCSUrl(repo string) (*url.URL, error) {
var (
repoURL *url.URL
err error
)
if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil {
// Match SCP-like syntax and convert it to a URL.
// Eg, "git@github.com:user/repo" becomes
// "ssh://git@github.com/user/repo".
repoURL = &url.URL{
Scheme: "ssh",
User: url.User(m[1]),
Host: m[2],
Path: m[3],
}
} else {
if !strings.Contains(repo, "//") {
repo = "//" + repo
}
if strings.HasPrefix(repo, "//git@") {
repo = "ssh:" + repo
} else if strings.HasPrefix(repo, "//") {
repo = "https:" + repo
}
repoURL, err = url.Parse(repo)
if err != nil {
return nil, err
}
}
// Iterate over insecure schemes too, because this function simply
// reports the state of the repo. If we can't see insecure schemes then
// we can't report the actual repo URL.
for _, s := range scheme {
if repoURL.Scheme == s {
return repoURL, nil
}
}
return nil, errors.New("unable to parse repo url")
}

@ -1,55 +0,0 @@
package base
import (
"net"
"strings"
"testing"
)
func TestParseVCSUrl(t *testing.T) {
repos := []string{
// ssh://[user@]host.xz[:port]/path/to/repo.git/
"ssh://git@github.com:7875/go-kratos/kratos.git",
// git://host.xz[:port]/path/to/repo.git/
"git://github.com:7875/go-kratos/kratos.git",
// http[s]://host.xz[:port]/path/to/repo.git/
"https://github.com:7875/go-kratos/kratos.git",
// ftp[s]://host.xz[:port]/path/to/repo.git/
"ftps://github.com:7875/go-kratos/kratos.git",
//[user@]host.xz:path/to/repo.git/
"git@github.com:go-kratos/kratos.git",
// ssh://[user@]host.xz[:port]/~[user]/path/to/repo.git/
"ssh://git@github.com:7875/go-kratos/kratos.git",
// git://host.xz[:port]/~[user]/path/to/repo.git/
"git://github.com:7875/go-kratos/kratos.git",
//[user@]host.xz:/~[user]/path/to/repo.git/
"git@github.com:go-kratos/kratos.git",
///path/to/repo.git/
"~/go-kratos/kratos.git",
// file:///path/to/repo.git/
"file://~/go-kratos/kratos.git",
}
for _, repo := range repos {
url, err := ParseVCSUrl(repo)
if err != nil {
t.Fatal(repo, err)
}
urlPath := strings.TrimLeft(url.Path, "/")
if urlPath != "go-kratos/kratos.git" {
t.Fatal(repo, "parse url failed", urlPath)
}
}
}
func TestParseSsh(t *testing.T) {
repo := "ssh://git@github.com:7875/go-kratos/kratos.git"
url, err := ParseVCSUrl(repo)
if err != nil {
t.Fatal(err)
}
host, _, err := net.SplitHostPort(url.Host)
if err != nil {
host = url.Host
}
t.Log(host, url.Path)
}

@ -28,7 +28,7 @@ func init() {
token = os.Getenv("GITHUB_TOKEN")
}
func run(_ *cobra.Command, args []string) {
func run(cmd *cobra.Command, args []string) {
owner, repo := ParseGithubURL(repoURL)
api := GithubAPI{Owner: owner, Repo: repo, Token: token}
version := "latest"

@ -43,7 +43,7 @@ func (g *GithubAPI) GetReleaseInfo(version string) ReleaseInfo {
if version != "latest" {
api = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", g.Owner, g.Repo, version)
}
resp, code := requestGithubAPI(api, http.MethodGet, nil, g.Token)
resp, code := requestGithubAPI(api, "GET", nil, g.Token)
if code != http.StatusOK {
printGithubErrorInfo(resp)
}
@ -59,11 +59,10 @@ func (g *GithubAPI) GetReleaseInfo(version string) ReleaseInfo {
func (g *GithubAPI) GetCommitsInfo() []CommitInfo {
info := g.GetReleaseInfo("latest")
page := 1
prePage := 100
var list []CommitInfo
for {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits?pre_page=%d&page=%d&since=%s", g.Owner, g.Repo, prePage, page, info.PublishedAt)
resp, code := requestGithubAPI(url, http.MethodGet, nil, g.Token)
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits?pre_page=100&page=%d&since=%s", g.Owner, g.Repo, page, info.PublishedAt)
resp, code := requestGithubAPI(url, "GET", nil, g.Token)
if code != http.StatusOK {
printGithubErrorInfo(resp)
}
@ -73,7 +72,7 @@ func (g *GithubAPI) GetCommitsInfo() []CommitInfo {
fatal(err)
}
list = append(list, res...)
if len(res) < prePage {
if len(res) < http.StatusContinue {
break
}
page++

@ -1,25 +0,0 @@
package change
import "testing"
func TestParseGithubURL(t *testing.T) {
urls := []struct {
url string
owner string
repo string
}{
{"https://github.com/go-kratos/kratos.git", "go-kratos", "kratos"},
{"https://github.com/go-kratos/kratos", "go-kratos", "kratos"},
{"git@github.com:go-kratos/kratos.git", "go-kratos", "kratos"},
{"https://github.com/go-kratos/go-kratos.dev.git", "go-kratos", "go-kratos.dev"},
}
for _, url := range urls {
owner, repo := ParseGithubURL(url.url)
if owner != url.owner {
t.Fatalf("owner want: %s, got: %s", owner, url.owner)
}
if repo != url.repo {
t.Fatalf("repo want: %s, got: %s", repo, url.repo)
}
}
}

@ -4,20 +4,19 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"path"
"github.com/AlecAivazis/survey/v2"
"github.com/fatih/color"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base"
)
var repoAddIgnores = []string{
".git", ".github", "api", "README.md", "LICENSE", "go.mod", "go.sum", "third_party", "openapi.yaml", ".gitignore",
".git", ".github", "api", "README.md", "LICENSE", "go.mod", "go.sum", "third_party",
}
func (p *Project) Add(ctx context.Context, dir string, layout string, branch string, mod string) error {
to := filepath.Join(dir, p.Name)
to := path.Join(dir, p.Path)
if _, err := os.Stat(to); !os.IsNotExist(err) {
fmt.Printf("🚫 %s already exists\n", p.Name)
@ -40,13 +39,13 @@ func (p *Project) Add(ctx context.Context, dir string, layout string, branch str
repo := base.NewRepo(layout, branch)
if err := repo.CopyToV2(ctx, to, filepath.Join(mod, p.Path), repoAddIgnores, []string{filepath.Join(p.Path, "api"), "api"}); err != nil {
if err := repo.CopyToV2(ctx, to, path.Join(mod, p.Path), repoAddIgnores, []string{path.Join(p.Path, "api"), "api"}); err != nil {
return err
}
e := os.Rename(
filepath.Join(to, "cmd", "server"),
filepath.Join(to, "cmd", p.Name),
path.Join(to, "cmd", "server"),
path.Join(to, "cmd", p.Name),
)
if e != nil {
return e

@ -4,12 +4,12 @@ import (
"context"
"fmt"
"os"
"path/filepath"
"path"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base"
"github.com/AlecAivazis/survey/v2"
"github.com/fatih/color"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base"
)
// Project is a project template.
@ -20,14 +20,14 @@ type Project struct {
// New new a project from remote repo.
func (p *Project) New(ctx context.Context, dir string, layout string, branch string) error {
to := filepath.Join(dir, p.Name)
to := path.Join(dir, p.Name)
if _, err := os.Stat(to); !os.IsNotExist(err) {
fmt.Printf("🚫 %s already exists\n", p.Name)
override := false
prompt := &survey.Confirm{
Message: "📂 Do you want to override the folder ?",
Help: "Delete the existing folder and create the project.",
}
var override bool
e := survey.AskOne(prompt, &override)
if e != nil {
return e
@ -39,12 +39,12 @@ func (p *Project) New(ctx context.Context, dir string, layout string, branch str
}
fmt.Printf("🚀 Creating service %s, layout repo is %s, please wait a moment.\n\n", p.Name, layout)
repo := base.NewRepo(layout, branch)
if err := repo.CopyTo(ctx, to, p.Name, []string{".git", ".github"}); err != nil {
if err := repo.CopyTo(ctx, to, p.Path, []string{".git", ".github"}); err != nil {
return err
}
e := os.Rename(
filepath.Join(to, "cmd", "server"),
filepath.Join(to, "cmd", p.Name),
path.Join(to, "cmd", "server"),
path.Join(to, "cmd", p.Name),
)
if e != nil {
return e

@ -5,14 +5,12 @@ import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"path"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base"
"github.com/spf13/cobra"
)
// CmdNew represents the new command.
@ -41,7 +39,7 @@ func init() {
CmdNew.Flags().BoolVarP(&nomod, "nomod", "", nomod, "retain go mod")
}
func run(_ *cobra.Command, args []string) {
func run(cmd *cobra.Command, args []string) {
wd, err := os.Getwd()
if err != nil {
panic(err)
@ -65,34 +63,23 @@ func run(_ *cobra.Command, args []string) {
} else {
name = args[0]
}
projectName, workingDir := processProjectParams(name, wd)
p := &Project{Name: projectName}
p := &Project{Name: path.Base(name), Path: name}
done := make(chan error, 1)
go func() {
if !nomod {
done <- p.New(ctx, workingDir, repoURL, branch)
done <- p.New(ctx, wd, repoURL, branch)
return
}
projectRoot := getgomodProjectRoot(workingDir)
if gomodIsNotExistIn(projectRoot) {
done <- fmt.Errorf("🚫 go.mod don't exists in %s", projectRoot)
return
}
p.Path, err = filepath.Rel(projectRoot, filepath.Join(workingDir, projectName))
if err != nil {
done <- fmt.Errorf("🚫 failed to get relative path: %v", err)
if _, e := os.Stat(path.Join(wd, "go.mod")); os.IsNotExist(e) {
done <- fmt.Errorf("🚫 go.mod don't exists in %s", wd)
return
}
mod, e := base.ModulePath(filepath.Join(projectRoot, "go.mod"))
mod, e := base.ModulePath(path.Join(wd, "go.mod"))
if e != nil {
done <- fmt.Errorf("🚫 failed to parse `go.mod`: %v", e)
return
panic(e)
}
// Get the relative path for adding a project based on Go modules
p.Path = filepath.Join(strings.TrimPrefix(workingDir, projectRoot+"/"), p.Name)
done <- p.Add(ctx, workingDir, repoURL, branch, mod)
done <- p.Add(ctx, wd, repoURL, branch, mod)
}()
select {
case <-ctx.Done():
@ -107,43 +94,3 @@ func run(_ *cobra.Command, args []string) {
}
}
}
func processProjectParams(projectName string, workingDir string) (projectNameResult, workingDirResult string) {
_projectDir := projectName
_workingDir := workingDir
// Process ProjectName with system variable
if strings.HasPrefix(projectName, "~") {
homeDir, err := os.UserHomeDir()
if err != nil {
// cannot get user home return fallback place dir
return _projectDir, _workingDir
}
_projectDir = filepath.Join(homeDir, projectName[2:])
}
// check path is relative
if !filepath.IsAbs(projectName) {
absPath, err := filepath.Abs(projectName)
if err != nil {
return _projectDir, _workingDir
}
_projectDir = absPath
}
return filepath.Base(_projectDir), filepath.Dir(_projectDir)
}
func getgomodProjectRoot(dir string) string {
if dir == filepath.Dir(dir) {
return dir
}
if gomodIsNotExistIn(dir) {
return getgomodProjectRoot(filepath.Dir(dir))
}
return dir
}
func gomodIsNotExistIn(dir string) bool {
_, e := os.Stat(filepath.Join(dir, "go.mod"))
return os.IsNotExist(e)
}

@ -1,29 +0,0 @@
//go:build linux
// +build linux
package project
import (
"testing"
)
func Test_processProjectParams(t *testing.T) {
type args struct {
projectName string
fallbackPlaceDir string
}
tests := []struct {
name string
args args
want string
}{
{"absLinux", args{projectName: "/home/kratos/awesome/go/demo", fallbackPlaceDir: ""}, "/home/kratos/awesome/go"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if _, got := processProjectParams(tt.args.projectName, tt.args.fallbackPlaceDir); got != tt.want {
t.Errorf("processProjectParams() = %v, want %v", got, tt.want)
}
})
}
}

@ -1,144 +0,0 @@
package project
import (
"fmt"
"go/parser"
"go/token"
"os"
"path/filepath"
"testing"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base"
)
// TestCmdNew tests the `kratos new` command.
func TestCmdNew(t *testing.T) {
cwd := changeCurrentDir(t)
projectName := "helloworld"
// create a new project
CmdNew.SetArgs([]string{projectName})
if err := CmdNew.Execute(); err != nil {
t.Fatalf("executing command: %v", err)
}
// check that the expected files were created
for _, file := range []string{
"go.mod",
"go.sum",
"README.md",
"cmd/helloworld/main.go",
} {
if _, err := os.Stat(filepath.Join(cwd, projectName, file)); err != nil {
t.Errorf("expected file %s to exist", file)
}
}
// check that the go.mod file contains the expected module name
assertGoMod(t, filepath.Join(cwd, projectName, "go.mod"), projectName)
assertImportsInclude(t, filepath.Join(cwd, projectName, "cmd", projectName, "wire.go"), fmt.Sprintf(`"%s/internal/biz"`, projectName))
}
// TestCmdNewNoMod tests the `kratos new` command with the --nomod flag.
func TestCmdNewNoMod(t *testing.T) {
cwd := changeCurrentDir(t)
// create a new project
CmdNew.SetArgs([]string{"project"})
if err := CmdNew.Execute(); err != nil {
t.Fatalf("executing command: %v", err)
}
// add new app with --nomod flag
CmdNew.SetArgs([]string{"--nomod", "project/app/user"})
if err := CmdNew.Execute(); err != nil {
t.Fatalf("executing command: %v", err)
}
// check that the expected files were created
for _, file := range []string{
"go.mod",
"go.sum",
"README.md",
"cmd/project/main.go",
"app/user/cmd/user/main.go",
} {
if _, err := os.Stat(filepath.Join(cwd, "project", file)); err != nil {
t.Errorf("expected file %s to exist", file)
}
}
assertImportsInclude(t, filepath.Join(cwd, "project/app/user/cmd/user/wire.go"), `"project/app/user/internal/biz"`)
}
// assertImportsInclude checks that the file at path contains the expected import.
func assertImportsInclude(t *testing.T, path, expected string) {
t.Helper()
got, err := imports(path)
if err != nil {
t.Fatalf("getting imports: %v", err)
}
for _, imp := range got {
if imp == expected {
return
}
}
t.Errorf("expected imports to include %s, got %v", expected, got)
}
// imports returns the imports in the file at path.
func imports(path string) ([]string, error) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
if err != nil {
return nil, err
}
imports := make([]string, 0, len(f.Imports))
for _, s := range f.Imports {
imports = append(imports, s.Path.Value)
}
return imports, nil
}
// assertGoMod checks that the go.mod file contains the expected module name.
func assertGoMod(t *testing.T, path, expected string) {
t.Helper()
got, err := base.ModulePath(path)
if err != nil {
t.Fatalf("getting module path: %v", err)
}
if got != expected {
t.Errorf("expected module name %s, got %s", expected, got)
}
}
// change the working directory to the tempdir
func changeCurrentDir(t *testing.T) string {
t.Helper()
tmp := t.TempDir()
oldCWD, err := os.Getwd()
if err != nil {
t.Fatalf("getting working directory: %v", err)
}
if err := os.Chdir(tmp); err != nil {
t.Fatalf("changing working directory: %v", err)
}
t.Cleanup(func() {
if err := os.Chdir(oldCWD); err != nil {
t.Fatalf("restoring working directory: %v", err)
}
})
return tmp
}

@ -1,30 +0,0 @@
//go:build windows
// +build windows
package project
import (
"testing"
)
func Test_processProjectParams(t *testing.T) {
type args struct {
projectName string
fallbackPlaceDir string
}
tests := []struct {
name string
args args
want string
}{
{"absWindows", args{projectName: "c:\\kratos\\awesome\\go\\demo", fallbackPlaceDir: ""}, "c:\\kratos\\awesome\\go"},
//{"relativeWindows", args{projectName: "/home/kratos/awesome/go/demo", fallbackPlaceDir: ""}, "/home/kratos/awesome/go"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if _, got := processProjectParams(tt.args.projectName, tt.args.fallbackPlaceDir); got != tt.want {
t.Errorf("getProjectPlaceDir() = %v, want %v", got, tt.want)
}
})
}
}

@ -7,23 +7,18 @@ import (
"github.com/spf13/cobra"
"golang.org/x/mod/modfile"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
// CmdAdd represents the add command.
var CmdAdd = &cobra.Command{
Use: "add",
Short: "Add a proto API template",
Long: "Add a proto API template. Example: kratos proto add helloworld/v1/hello.proto",
Long: "Add a proto API template. Example: kratos add helloworld/v1/hello.proto",
Run: run,
}
func run(_ *cobra.Command, args []string) {
if len(args) == 0 {
fmt.Println("Please enter the proto file or directory")
return
}
func run(cmd *cobra.Command, args []string) {
// kratos proto add helloworld/v1/helloworld.proto
input := args[0]
n := strings.LastIndex(input, "/")
if n == -1 {
@ -68,11 +63,7 @@ func javaPackage(name string) string {
}
func serviceName(name string) string {
return toUpperCamelCase(strings.Split(name, ".")[0])
return export(strings.Split(name, ".")[0])
}
func toUpperCamelCase(s string) string {
s = strings.ReplaceAll(s, "_", " ")
s = cases.Title(language.Und, cases.NoLower).String(s)
return strings.ReplaceAll(s, " ", "")
}
func export(s string) string { return strings.ToUpper(s[:1]) + s[1:] }

@ -1,38 +0,0 @@
package add
import "testing"
func TestUnderscoreToUpperCamelCase(t *testing.T) {
tests := []struct {
name string
want string
}{
{
name: "hello_world",
want: "HelloWorld",
},
{
name: "v2_kratos_dev",
want: "V2KratosDev",
},
{
name: "www_Google_com",
want: "WwwGoogleCom",
},
{
name: "wwwBaidu_com",
want: "WwwBaiduCom",
},
{
name: "HelloWorld",
want: "HelloWorld",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := toUpperCamelCase(tt.name); got != tt.want {
t.Errorf("toUpperCamelCase() = %v, want %v", got, tt.want)
}
})
}
}

@ -3,7 +3,7 @@ package add
import (
"fmt"
"os"
"path/filepath"
"path"
)
// Proto is a proto generator.
@ -26,13 +26,13 @@ func (p *Proto) Generate() error {
if err != nil {
panic(err)
}
to := filepath.Join(wd, p.Path)
to := path.Join(wd, p.Path)
if _, err := os.Stat(to); os.IsNotExist(err) {
if err := os.MkdirAll(to, 0o700); err != nil {
return err
}
}
name := filepath.Join(to, p.Name)
name := path.Join(to, p.Name)
if _, err := os.Stat(name); !os.IsNotExist(err) {
return fmt.Errorf("%s already exists", p.Name)
}

@ -8,9 +8,9 @@ import (
"regexp"
"strings"
"github.com/spf13/cobra"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base"
"github.com/spf13/cobra"
)
// CmdClient represents the source command.
@ -30,7 +30,7 @@ func init() {
CmdClient.Flags().StringVarP(&protoPath, "proto_path", "p", protoPath, "proto path")
}
func run(_ *cobra.Command, args []string) {
func run(cmd *cobra.Command, args []string) {
if len(args) == 0 {
fmt.Println("Please enter the proto file or directory")
return

@ -1,11 +1,11 @@
package proto
import (
"github.com/spf13/cobra"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/proto/add"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/proto/client"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/proto/server"
"github.com/spf13/cobra"
)
// CmdProto represents the proto command.
@ -13,6 +13,7 @@ var CmdProto = &cobra.Command{
Use: "proto",
Short: "Generate the proto files",
Long: "Generate the proto files.",
Run: run,
}
func init() {
@ -20,3 +21,6 @@ func init() {
CmdProto.AddCommand(client.CmdClient)
CmdProto.AddCommand(server.CmdServer)
}
func run(cmd *cobra.Command, args []string) {
}

@ -4,20 +4,23 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"path"
"strings"
"github.com/emicklei/proto"
"github.com/spf13/cobra"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"github.com/emicklei/proto"
"github.com/spf13/cobra"
)
var caser = cases.Title(language.English)
// CmdServer the service command.
var CmdServer = &cobra.Command{
Use: "server",
Short: "Generate the proto server implementations",
Long: "Generate the proto server implementations. Example: kratos proto server api/xxx.proto --target-dir=internal/service",
Short: "Generate the proto Server implementations",
Long: "Generate the proto Server implementations. Example: kratos proto server api/xxx.proto -target-dir=internal/service",
Run: run,
}
var targetDir string
@ -26,7 +29,7 @@ func init() {
CmdServer.Flags().StringVarP(&targetDir, "target-dir", "t", "internal/service", "generate target directory")
}
func run(_ *cobra.Command, args []string) {
func run(cmd *cobra.Command, args []string) {
if len(args) == 0 {
fmt.Fprintln(os.Stderr, "Please specify the proto file. Example: kratos proto server api/xxx.proto")
return
@ -56,7 +59,7 @@ func run(_ *cobra.Command, args []string) {
proto.WithService(func(s *proto.Service) {
cs := &Service{
Package: pkg,
Service: serviceName(s.Name),
Service: s.Name,
}
for _, e := range s.Elements {
r, ok := e.(*proto.RPC)
@ -64,8 +67,8 @@ func run(_ *cobra.Command, args []string) {
continue
}
cs.Methods = append(cs.Methods, &Method{
Service: serviceName(s.Name), Name: serviceName(r.Name), Request: parametersName(r.RequestType),
Reply: parametersName(r.ReturnsType), Type: getMethodType(r.StreamsRequest, r.StreamsReturns),
Service: s.Name, Name: caser.String(r.Name), Request: r.RequestType,
Reply: r.ReturnsType, Type: getMethodType(r.StreamsRequest, r.StreamsReturns),
})
}
res = append(res, cs)
@ -76,7 +79,7 @@ func run(_ *cobra.Command, args []string) {
return
}
for _, s := range res {
to := filepath.Join(targetDir, strings.ToLower(s.Service)+".go")
to := path.Join(targetDir, strings.ToLower(s.Service)+".go")
if _, err := os.Stat(to); !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "%s already exists: %s\n", s.Service, to)
continue
@ -104,17 +107,3 @@ func getMethodType(streamsRequest, streamsReturns bool) MethodType {
}
return unaryType
}
func parametersName(name string) string {
return strings.ReplaceAll(name, ".", "_")
}
func serviceName(name string) string {
return toUpperCamelCase(strings.Split(name, ".")[0])
}
func toUpperCamelCase(s string) string {
s = strings.ReplaceAll(s, "_", " ")
s = cases.Title(language.Und, cases.NoLower).String(s)
return strings.ReplaceAll(s, " ", "")
}

@ -1,102 +0,0 @@
package server
import "testing"
func Test_serviceName(t *testing.T) {
type args struct {
str string
}
tests := []struct {
name string
args args
want string
}{
{
name: "serviceName on lowercase words",
args: args{str: "helloworld"},
want: "Helloworld",
},
{
name: "serviceName on uppercase words",
args: args{str: "HELLOWORLD"},
want: "HELLOWORLD",
},
{
name: "serviceName on lowercase words with spaces",
args: args{str: "hello world"},
want: "HelloWorld",
},
{
name: "serviceName on uppercase words with spaces",
args: args{str: "HELLO WORLD"},
want: "HELLOWORLD",
},
{
name: "serviceName on Lower Camel Case words",
args: args{str: "helloWorld"},
want: "HelloWorld",
},
{
name: "serviceName on Lower Camel Case words",
args: args{str: "helloWorld"},
want: "HelloWorld",
},
{
name: "serviceName on Upper Camel Case words",
args: args{str: "HelloWorld"},
want: "HelloWorld",
},
{
name: "serviceName on Upper Camel Case words",
args: args{str: "hello_world"},
want: "HelloWorld",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := serviceName(tt.args.str); got != tt.want {
t.Errorf("serviceName() = %v, want %v", got, tt.want)
}
})
}
}
func Test_parametersName(t *testing.T) {
type args struct {
name string
}
tests := []struct {
name string
args args
want string
}{
{
name: "parametersName on not nested",
args: args{
name: "MessageResponse",
},
want: "MessageResponse",
},
{
name: "parametersName on One layer of nesting",
args: args{
name: "Message.Response",
},
want: "Message_Response",
},
{
name: "parametersName on Two layer of nesting",
args: args{
name: "Message.Message2.Response",
},
want: "Message_Message2_Response",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := parametersName(tt.args.name); got != tt.want {
t.Errorf("parametersName() = %v, want %v", got, tt.want)
}
})
}
}

@ -5,7 +5,6 @@ import (
"html/template"
)
//nolint:lll
var serviceTemplate = `
{{- /* delete empty line */ -}}
package service
@ -35,7 +34,8 @@ func New{{ .Service }}Service() *{{ .Service }}Service {
{{- $s1 := "google.protobuf.Empty" }}
{{ range .Methods }}
{{- if eq .Type 1 }}
func (s *{{ .Service }}Service) {{ .Name }}(ctx context.Context, req {{ if eq .Request $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Request }}{{ end }}) ({{ if eq .Reply $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Reply }}{{ end }}, error) {
func (s *{{ .Service }}Service) {{ .Name }}(ctx context.Context, req {{ if eq .Request $s1 }}*emptypb.Empty
{{ else }}*pb.{{ .Request }}{{ end }}) ({{ if eq .Reply $s1 }}*emptypb.Empty{{ else }}*pb.{{ .Reply }}{{ end }}, error) {
return {{ if eq .Reply $s1 }}&emptypb.Empty{}{{ else }}&pb.{{ .Reply }}{}{{ end }}, nil
}

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
@ -18,18 +19,12 @@ var CmdRun = &cobra.Command{
Long: "Run project. Example: kratos run",
Run: Run,
}
var targetDir string
func init() {
CmdRun.Flags().StringVarP(&targetDir, "work", "w", "", "target working directory")
}
// Run run project.
func Run(cmd *cobra.Command, args []string) {
var dir string
cmdArgs, programArgs := splitArgs(cmd, args)
if len(cmdArgs) > 0 {
dir = cmdArgs[0]
if len(args) > 0 {
dir = args[0]
}
base, err := os.Getwd()
if err != nil {
@ -68,25 +63,16 @@ func Run(cmd *cobra.Command, args []string) {
dir = cmdPath[dir]
}
}
fd := exec.Command("go", append([]string{"run", dir}, programArgs...)...)
fd := exec.Command("go", "run", ".")
fd.Stdout = os.Stdout
fd.Stderr = os.Stderr
fd.Dir = dir
changeWorkingDirectory(fd, targetDir)
if err := fd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "\033[31mERROR: %s\033[m\n", err.Error())
return
}
}
func splitArgs(cmd *cobra.Command, args []string) (cmdArgs, programArgs []string) {
dashAt := cmd.ArgsLenAtDash()
if dashAt >= 0 {
return args[:dashAt], args[dashAt:]
}
return args, []string{}
}
func findCMD(base string) (map[string]string, error) {
wd, err := os.Getwd()
if err != nil {
@ -107,7 +93,7 @@ func findCMD(base string) (map[string]string, error) {
}
for _, fileInfo := range paths {
if fileInfo.IsDir() {
abs := filepath.Join(walkPath, fileInfo.Name())
abs := path.Join(walkPath, fileInfo.Name())
cmdPath[strings.TrimPrefix(abs, wd)] = abs
}
}
@ -136,10 +122,3 @@ func findCMD(base string) (map[string]string, error) {
}
return map[string]string{"": base}, nil
}
func changeWorkingDirectory(cmd *exec.Cmd, targetDir string) {
targetDir = strings.TrimSpace(targetDir)
if targetDir != "" {
cmd.Dir = targetDir
}
}

@ -3,9 +3,9 @@ package upgrade
import (
"fmt"
"github.com/spf13/cobra"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base"
"github.com/spf13/cobra"
)
// CmdUpgrade represents the upgrade command.
@ -17,7 +17,7 @@ var CmdUpgrade = &cobra.Command{
}
// Run upgrade the kratos tools.
func Run(_ *cobra.Command, _ []string) {
func Run(cmd *cobra.Command, args []string) {
err := base.GoInstall(
"github.com/go-kratos/kratos/cmd/kratos/v2@latest",
"github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest",

@ -3,13 +3,13 @@ package main
import (
"log"
"github.com/spf13/cobra"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/change"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/project"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/proto"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/run"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/upgrade"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{

@ -1,4 +1,4 @@
package main
// release is the current kratos tool version.
const release = "v2.6.3"
const release = "v2.3.0"

@ -8,10 +8,9 @@ import (
"golang.org/x/text/cases"
"golang.org/x/text/language"
"github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2/errors"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
"github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2/errors"
)
const (
@ -59,7 +58,7 @@ func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.
}
}
func genErrorsReason(_ *protogen.Plugin, _ *protogen.File, g *protogen.GeneratedFile, enum *protogen.Enum) bool {
func genErrorsReason(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, enum *protogen.Enum) bool {
defaultCode := proto.GetExtension(enum.Desc.Options(), errors.E_DefaultCode)
code := 0
if ok := defaultCode.(int32); ok != 0 {

@ -1,17 +0,0 @@
{{ range .Errors }}
{{ if .HasComment }}{{ .Comment }}{{ end -}}
func Is{{.CamelValue}}(err error) bool {
if err == nil {
return false
}
e := errors.FromError(err)
return e.Reason == {{ .Name }}_{{ .Value }}.String() && e.Code == {{ .HTTPCode }}
}
{{ if .HasComment }}{{ .Comment }}{{ end -}}
func Error{{ .CamelValue }}(format string, args ...interface{}) *errors.Error {
return errors.New({{ .HTTPCode }}, {{ .Name }}_{{ .Value }}.String(), fmt.Sprintf(format, args...))
}
{{- end }}

@ -3,6 +3,6 @@ module github.com/go-kratos/kratos/cmd/protoc-gen-go-errors/v2
go 1.16
require (
golang.org/x/text v0.3.8
golang.org/x/text v0.3.7
google.golang.org/protobuf v1.28.0
)

@ -1,31 +1,9 @@
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=

@ -2,12 +2,28 @@ package main
import (
"bytes"
_ "embed"
"text/template"
)
//go:embed errorsTemplate.tpl
var errorsTemplate string
var errorsTemplate = `
{{ range .Errors }}
{{ if .HasComment }}{{ .Comment }}{{ end -}}
func Is{{.CamelValue}}(err error) bool {
if err == nil {
return false
}
e := errors.FromError(err)
return e.Reason == {{ .Name }}_{{ .Value }}.String() && e.Code == {{ .HTTPCode }}
}
{{ if .HasComment }}{{ .Comment }}{{ end -}}
func Error{{ .CamelValue }}(format string, args ...interface{}) *errors.Error {
return errors.New({{ .HTTPCode }}, {{ .Name }}_{{ .Value }}.String(), fmt.Sprintf(format, args...))
}
{{- end }}
`
type errorInfo struct {
Name string

@ -1,4 +1,4 @@
package main
// release is the current protoc-gen-go-errors version.
const release = "v2.6.3"
const release = "v2.3.0"

@ -2,7 +2,6 @@ package main
import (
"fmt"
"net/http"
"os"
"regexp"
"strings"
@ -24,7 +23,7 @@ const (
var methodSets = make(map[string]int)
// generateFile generates a _http.pb.go file containing kratos errors definitions.
func generateFile(gen *protogen.Plugin, file *protogen.File, omitempty bool, omitemptyPrefix string) *protogen.GeneratedFile {
func generateFile(gen *protogen.Plugin, file *protogen.File, omitempty bool) *protogen.GeneratedFile {
if len(file.Services) == 0 || (omitempty && !hasHTTPRule(file.Services)) {
return nil
}
@ -32,22 +31,16 @@ func generateFile(gen *protogen.Plugin, file *protogen.File, omitempty bool, omi
g := gen.NewGeneratedFile(filename, file.GoImportPath)
g.P("// Code generated by protoc-gen-go-http. DO NOT EDIT.")
g.P("// versions:")
g.P(fmt.Sprintf("// - protoc-gen-go-http %s", release))
g.P("// - protoc ", protocVersion(gen))
if file.Proto.GetOptions().GetDeprecated() {
g.P("// ", file.Desc.Path(), " is a deprecated file.")
} else {
g.P("// source: ", file.Desc.Path())
}
g.P(fmt.Sprintf("// protoc-gen-go-http %s", release))
g.P()
g.P("package ", file.GoPackageName)
g.P()
generateFileContent(gen, file, g, omitempty, omitemptyPrefix)
generateFileContent(gen, file, g, omitempty)
return g
}
// generateFileContent generates the kratos errors definitions, excluding the package statement.
func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, omitempty bool, omitemptyPrefix string) {
func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, omitempty bool) {
if len(file.Services) == 0 {
return
}
@ -59,11 +52,11 @@ func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.
g.P()
for _, service := range file.Services {
genService(gen, file, g, service, omitempty, omitemptyPrefix)
genService(gen, file, g, service, omitempty)
}
}
func genService(_ *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, service *protogen.Service, omitempty bool, omitemptyPrefix string) {
func genService(gen *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFile, service *protogen.Service, omitempty bool) {
if service.Desc.Options().(*descriptorpb.ServiceOptions).GetDeprecated() {
g.P("//")
g.P(deprecationComment)
@ -81,12 +74,12 @@ func genService(_ *protogen.Plugin, file *protogen.File, g *protogen.GeneratedFi
rule, ok := proto.GetExtension(method.Desc.Options(), annotations.E_Http).(*annotations.HttpRule)
if rule != nil && ok {
for _, bind := range rule.AdditionalBindings {
sd.Methods = append(sd.Methods, buildHTTPRule(g, service, method, bind, omitemptyPrefix))
sd.Methods = append(sd.Methods, buildHTTPRule(g, method, bind))
}
sd.Methods = append(sd.Methods, buildHTTPRule(g, service, method, rule, omitemptyPrefix))
sd.Methods = append(sd.Methods, buildHTTPRule(g, method, rule))
} else if !omitempty {
path := fmt.Sprintf("%s/%s/%s", omitemptyPrefix, service.Desc.FullName(), method.Desc.Name())
sd.Methods = append(sd.Methods, buildMethodDesc(g, method, http.MethodPost, path))
path := fmt.Sprintf("/%s/%s", service.Desc.FullName(), method.Desc.Name())
sd.Methods = append(sd.Methods, buildMethodDesc(g, method, "POST", path))
}
}
if len(sd.Methods) != 0 {
@ -109,7 +102,7 @@ func hasHTTPRule(services []*protogen.Service) bool {
return false
}
func buildHTTPRule(g *protogen.GeneratedFile, service *protogen.Service, m *protogen.Method, rule *annotations.HttpRule, omitemptyPrefix string) *methodDesc {
func buildHTTPRule(g *protogen.GeneratedFile, m *protogen.Method, rule *annotations.HttpRule) *methodDesc {
var (
path string
method string
@ -120,33 +113,27 @@ func buildHTTPRule(g *protogen.GeneratedFile, service *protogen.Service, m *prot
switch pattern := rule.Pattern.(type) {
case *annotations.HttpRule_Get:
path = pattern.Get
method = http.MethodGet
method = "GET"
case *annotations.HttpRule_Put:
path = pattern.Put
method = http.MethodPut
method = "PUT"
case *annotations.HttpRule_Post:
path = pattern.Post
method = http.MethodPost
method = "POST"
case *annotations.HttpRule_Delete:
path = pattern.Delete
method = http.MethodDelete
method = "DELETE"
case *annotations.HttpRule_Patch:
path = pattern.Patch
method = http.MethodPatch
method = "PATCH"
case *annotations.HttpRule_Custom:
path = pattern.Custom.Path
method = pattern.Custom.Kind
}
if method == "" {
method = http.MethodPost
}
if path == "" {
path = fmt.Sprintf("%s/%s/%s", omitemptyPrefix, service.Desc.FullName(), m.Desc.Name())
}
body = rule.Body
responseBody = rule.ResponseBody
md := buildMethodDesc(g, m, method, path)
if method == http.MethodGet || method == http.MethodDelete {
if method == "GET" || method == "DELETE" {
if body != "" {
_, _ = fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: %s %s body should not be declared.\n", method, path)
}
@ -204,17 +191,12 @@ func buildMethodDesc(g *protogen.GeneratedFile, m *protogen.Method, method, path
}
}
}
comment := m.Comments.Leading.String() + m.Comments.Trailing.String()
if comment != "" {
comment = "// " + m.GoName + strings.TrimPrefix(strings.TrimSuffix(comment, "\n"), "//")
}
return &methodDesc{
Name: m.GoName,
OriginalName: string(m.Desc.Name()),
Num: methodSets[m.GoName],
Request: g.QualifiedGoIdent(m.Input.GoIdent),
Reply: g.QualifiedGoIdent(m.Output.GoIdent),
Comment: comment,
Path: path,
Method: method,
HasVars: len(vars) > 0,
@ -225,9 +207,9 @@ func buildPathVars(path string) (res map[string]*string) {
if strings.HasSuffix(path, "/") {
fmt.Fprintf(os.Stderr, "\u001B[31mWARN\u001B[m: Path %s should not end with \"/\" \n", path)
}
pattern := regexp.MustCompile(`(?i){([a-z.0-9_\s]*)=?([^{}]*)}`)
res = make(map[string]*string)
pattern := regexp.MustCompile(`(?i){([a-z\.0-9_\s]*)=?([^{}]*)}`)
matches := pattern.FindAllStringSubmatch(path, -1)
res = make(map[string]*string, len(matches))
for _, m := range matches {
name := strings.TrimSpace(m[1])
if len(name) > 1 && len(m[2]) > 0 {
@ -267,8 +249,8 @@ func camelCaseVars(s string) string {
// drop the underscore and convert the letter to upper case.
// There is a remote possibility of this rewrite causing a name collision,
// but it's so remote we're prepared to pretend it's nonexistent - since the
// C++ generator lowercase names, it's extremely unlikely to have two fields
// with different capitalization.
// C++ generator lowercases names, it's extremely unlikely to have two fields
// with different capitalizations.
// In short, _my_field_name_2 becomes XMyFieldName_2.
func camelCase(s string) string {
if s == "" {
@ -319,16 +301,4 @@ func isASCIIDigit(c byte) bool {
return '0' <= c && c <= '9'
}
func protocVersion(gen *protogen.Plugin) string {
v := gen.Request.GetCompilerVersion()
if v == nil {
return "(unknown)"
}
var suffix string
if s := v.GetSuffix(); s != "" {
suffix = "-" + s
}
return fmt.Sprintf("v%d.%d.%d%s", v.GetMajor(), v.GetMinor(), v.GetPatch(), suffix)
}
const deprecationComment = "// Deprecated: Do not use."

@ -1,93 +0,0 @@
{{$svrType := .ServiceType}}
{{$svrName := .ServiceName}}
{{- range .MethodSets}}
const Operation{{$svrType}}{{.OriginalName}} = "/{{$svrName}}/{{.OriginalName}}"
{{- end}}
type {{.ServiceType}}HTTPServer interface {
{{- range .MethodSets}}
{{- if ne .Comment ""}}
{{.Comment}}
{{- end}}
{{.Name}}(context.Context, *{{.Request}}) (*{{.Reply}}, error)
{{- end}}
}
func Register{{.ServiceType}}HTTPServer(s *http.Server, srv {{.ServiceType}}HTTPServer) {
r := s.Route("/")
{{- range .Methods}}
r.{{.Method}}("{{.Path}}", _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv))
{{- end}}
}
{{range .Methods}}
func _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv {{$svrType}}HTTPServer) func(ctx http.Context) error {
return func(ctx http.Context) error {
var in {{.Request}}
{{- if .HasBody}}
if err := ctx.Bind(&in{{.Body}}); err != nil {
return err
}
{{- if not (eq .Body "")}}
if err := ctx.BindQuery(&in); err != nil {
return err
}
{{- end}}
{{- else}}
if err := ctx.BindQuery(&in{{.Body}}); err != nil {
return err
}
{{- end}}
{{- if .HasVars}}
if err := ctx.BindVars(&in); err != nil {
return err
}
{{- end}}
http.SetOperation(ctx,Operation{{$svrType}}{{.OriginalName}})
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.{{.Name}}(ctx, req.(*{{.Request}}))
})
out, err := h(ctx, &in)
if err != nil {
return err
}
reply := out.(*{{.Reply}})
return ctx.Result(200, reply{{.ResponseBody}})
}
}
{{end}}
type {{.ServiceType}}HTTPClient interface {
{{- range .MethodSets}}
{{.Name}}(ctx context.Context, req *{{.Request}}, opts ...http.CallOption) (rsp *{{.Reply}}, err error)
{{- end}}
}
type {{.ServiceType}}HTTPClientImpl struct{
cc *http.Client
}
func New{{.ServiceType}}HTTPClient (client *http.Client) {{.ServiceType}}HTTPClient {
return &{{.ServiceType}}HTTPClientImpl{client}
}
{{range .MethodSets}}
func (c *{{$svrType}}HTTPClientImpl) {{.Name}}(ctx context.Context, in *{{.Request}}, opts ...http.CallOption) (*{{.Reply}}, error) {
var out {{.Reply}}
pattern := "{{.Path}}"
path := binding.EncodeURL(pattern, in, {{not .HasBody}})
opts = append(opts, http.Operation(Operation{{$svrType}}{{.OriginalName}}))
opts = append(opts, http.PathTemplate(pattern))
{{if .HasBody -}}
err := c.cc.Invoke(ctx, "{{.Method}}", path, in{{.Body}}, &out{{.ResponseBody}}, opts...)
{{else -}}
err := c.cc.Invoke(ctx, "{{.Method}}", path, nil, &out{{.ResponseBody}}, opts...)
{{end -}}
if err != nil {
return nil, err
}
return &out, err
}
{{end}}

@ -11,7 +11,6 @@ import (
var (
showVersion = flag.Bool("version", false, "print the version and exit")
omitempty = flag.Bool("omitempty", true, "omit if google.api is empty")
omitemptyPrefix = flag.String("omitempty_prefix", "", "omit if google.api is empty")
)
func main() {
@ -28,7 +27,7 @@ func main() {
if !f.Generate {
continue
}
generateFile(gen, f, *omitempty, *omitemptyPrefix)
generateFile(gen, f, *omitempty)
}
return nil
})

@ -2,13 +2,97 @@ package main
import (
"bytes"
_ "embed"
"strings"
"text/template"
)
//go:embed httpTemplate.tpl
var httpTemplate string
var httpTemplate = `
{{$svrType := .ServiceType}}
{{$svrName := .ServiceName}}
type {{.ServiceType}}HTTPServer interface {
{{- range .MethodSets}}
{{.Name}}(context.Context, *{{.Request}}) (*{{.Reply}}, error)
{{- end}}
}
func Register{{.ServiceType}}HTTPServer(s *http.Server, srv {{.ServiceType}}HTTPServer) {
r := s.Route("/")
{{- range .Methods}}
r.{{.Method}}("{{.Path}}", _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv))
{{- end}}
}
{{range .Methods}}
func _{{$svrType}}_{{.Name}}{{.Num}}_HTTP_Handler(srv {{$svrType}}HTTPServer) func(ctx http.Context) error {
return func(ctx http.Context) error {
var in {{.Request}}
{{- if .HasBody}}
if err := ctx.Bind(&in{{.Body}}); err != nil {
return err
}
{{- if not (eq .Body "")}}
if err := ctx.BindQuery(&in); err != nil {
return err
}
{{- end}}
{{- else}}
if err := ctx.BindQuery(&in{{.Body}}); err != nil {
return err
}
{{- end}}
{{- if .HasVars}}
if err := ctx.BindVars(&in); err != nil {
return err
}
{{- end}}
http.SetOperation(ctx,"/{{$svrName}}/{{.OriginalName}}")
h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.{{.Name}}(ctx, req.(*{{.Request}}))
})
out, err := h(ctx, &in)
if err != nil {
return err
}
reply := out.(*{{.Reply}})
return ctx.Result(200, reply{{.ResponseBody}})
}
}
{{end}}
type {{.ServiceType}}HTTPClient interface {
{{- range .MethodSets}}
{{.Name}}(ctx context.Context, req *{{.Request}}, opts ...http.CallOption) (rsp *{{.Reply}}, err error)
{{- end}}
}
type {{.ServiceType}}HTTPClientImpl struct{
cc *http.Client
}
func New{{.ServiceType}}HTTPClient (client *http.Client) {{.ServiceType}}HTTPClient {
return &{{.ServiceType}}HTTPClientImpl{client}
}
{{range .MethodSets}}
func (c *{{$svrType}}HTTPClientImpl) {{.Name}}(ctx context.Context, in *{{.Request}}, opts ...http.CallOption) (*{{.Reply}}, error) {
var out {{.Reply}}
pattern := "{{.Path}}"
path := binding.EncodeURL(pattern, in, {{not .HasBody}})
opts = append(opts, http.Operation("/{{$svrName}}/{{.OriginalName}}"))
opts = append(opts, http.PathTemplate(pattern))
{{if .HasBody -}}
err := c.cc.Invoke(ctx, "{{.Method}}", path, in{{.Body}}, &out{{.ResponseBody}}, opts...)
{{else -}}
err := c.cc.Invoke(ctx, "{{.Method}}", path, nil, &out{{.ResponseBody}}, opts...)
{{end -}}
if err != nil {
return nil, err
}
return &out, err
}
{{end}}
`
type serviceDesc struct {
ServiceType string // Greeter
@ -25,7 +109,6 @@ type methodDesc struct {
Num int
Request string
Reply string
Comment string
// http_rule
Path string
Method string

@ -1,4 +1,4 @@
package main
// release is the current protoc-gen-go-http version.
const release = "v2.6.3"
const release = "v2.3.0"

@ -15,13 +15,13 @@ import (
"github.com/go-kratos/kratos/v2/log"
)
var _ Config = (*config)(nil)
var (
// ErrNotFound is key not found.
ErrNotFound = errors.New("key not found")
// ErrTypeAssert is type assert error.
ErrTypeAssert = errors.New("type assert error")
_ Config = (*config)(nil)
)
// Observer is config observer.
@ -44,7 +44,7 @@ type config struct {
watchers []Watcher
}
// New a config with options.
// New new a config with options.
func New(opts ...Option) Config {
o := options{
decoder: defaultDecoder,
@ -62,11 +62,11 @@ func New(opts ...Option) Config {
func (c *config) watch(w Watcher) {
for {
kvs, err := w.Next()
if err != nil {
if errors.Is(err, context.Canceled) {
log.Infof("watcher's ctx cancel : %v", err)
return
}
if err != nil {
time.Sleep(time.Second)
log.Errorf("failed to watch next config: %v", err)
continue

@ -2,6 +2,7 @@ package config
import (
"errors"
"reflect"
"testing"
)
@ -123,7 +124,7 @@ func TestConfig(t *testing.T) {
)
err = c.Close()
if err != nil {
t.Fatal(err)
t.Fatal("t is not nil")
}
jSource := newTestJSONSource(_testJSON)
@ -138,21 +139,21 @@ func TestConfig(t *testing.T) {
err = cf.Load()
if err != nil {
t.Fatal(err)
t.Fatal("t is not nil")
}
driver, err := cf.Value("data.database.driver").String()
val, err := cf.Value("data.database.driver").String()
if err != nil {
t.Fatal(err)
t.Fatal("t is not nil")
}
if databaseDriver != driver {
t.Fatal("databaseDriver is not equal to val")
if !reflect.DeepEqual(databaseDriver, val) {
t.Fatal(`databaseDriver is not equal to val`)
}
err = cf.Watch("endpoints", func(key string, value Value) {
})
if err != nil {
t.Fatal(err)
t.Fatal("t is not nil")
}
jSource.sig <- struct{}{}
@ -161,24 +162,24 @@ func TestConfig(t *testing.T) {
var testConf testConfigStruct
err = cf.Scan(&testConf)
if err != nil {
t.Fatal(err)
t.Fatal("t is not nil")
}
if httpAddr != testConf.Server.HTTP.Addr {
t.Errorf("testConf.Server.HTTP.Addr want: %s, got: %s", httpAddr, testConf.Server.HTTP.Addr)
if !reflect.DeepEqual(httpAddr, testConf.Server.HTTP.Addr) {
t.Fatal(`httpAddr is not equal to testConf.Server.HTTP.Addr`)
}
if httpTimeout != testConf.Server.HTTP.Timeout {
t.Errorf("testConf.Server.HTTP.Timeout want: %.1f, got: %.1f", httpTimeout, testConf.Server.HTTP.Timeout)
if !reflect.DeepEqual(httpTimeout, testConf.Server.HTTP.Timeout) {
t.Fatal(`httpTimeout is not equal to testConf.Server.HTTP.Timeout`)
}
if !testConf.Server.HTTP.EnableSSL {
t.Error("testConf.Server.HTTP.EnableSSL is not equal to true")
if !reflect.DeepEqual(true, testConf.Server.HTTP.EnableSSL) {
t.Fatal(`testConf.Server.HTTP.EnableSSL is not equal to true`)
}
if grpcPort != testConf.Server.GRPC.Port {
t.Errorf("testConf.Server.GRPC.Port want: %d, got: %d", grpcPort, testConf.Server.GRPC.Port)
if !reflect.DeepEqual(grpcPort, testConf.Server.GRPC.Port) {
t.Fatal(`grpcPort is not equal to testConf.Server.GRPC.Port`)
}
if endpoint1 != testConf.Endpoints[0] {
t.Errorf("testConf.Endpoints[0] want: %s, got: %s", endpoint1, testConf.Endpoints[0])
if !reflect.DeepEqual(endpoint1, testConf.Endpoints[0]) {
t.Fatal(`endpoint1 is not equal to testConf.Endpoints[0]`)
}
if len(testConf.Endpoints) != 2 {
t.Error("len(testConf.Endpoints) is not equal to 2")
if !reflect.DeepEqual(len(testConf.Endpoints), 2) {
t.Fatal(`len(testConf.Endpoints) is not equal to 2`)
}
}

16
config/env/env.go vendored

@ -8,29 +8,29 @@ import (
)
type env struct {
prefixes []string
prefixs []string
}
func NewSource(prefixes ...string) config.Source {
return &env{prefixes: prefixes}
func NewSource(prefixs ...string) config.Source {
return &env{prefixs: prefixs}
}
func (e *env) Load() (kv []*config.KeyValue, err error) {
return e.load(os.Environ()), nil
}
func (e *env) load(envs []string) []*config.KeyValue {
func (e *env) load(envStrings []string) []*config.KeyValue {
var kv []*config.KeyValue
for _, env := range envs {
for _, envstr := range envStrings {
var k, v string
subs := strings.SplitN(env, "=", 2) //nolint:gomnd
subs := strings.SplitN(envstr, "=", 2) //nolint:gomnd
k = subs[0]
if len(subs) > 1 {
v = subs[1]
}
if len(e.prefixes) > 0 {
p, ok := matchPrefix(e.prefixes, k)
if len(e.prefixs) > 0 {
p, ok := matchPrefix(e.prefixs, k)
if !ok || len(p) == len(k) {
continue
}

@ -261,7 +261,7 @@ func TestEnvWithoutPrefix(t *testing.T) {
func Test_env_load(t *testing.T) {
type fields struct {
prefixes []string
prefixs []string
}
type args struct {
envStrings []string
@ -275,7 +275,7 @@ func Test_env_load(t *testing.T) {
{
name: "without prefixes",
fields: fields{
prefixes: nil,
prefixs: nil,
},
args: args{
envStrings: []string{
@ -294,7 +294,7 @@ func Test_env_load(t *testing.T) {
{
name: "empty prefix",
fields: fields{
prefixes: []string{""},
prefixs: []string{""},
},
args: args{
envStrings: []string{
@ -313,7 +313,7 @@ func Test_env_load(t *testing.T) {
{
name: "underscore prefix",
fields: fields{
prefixes: []string{"_"},
prefixs: []string{"_"},
},
args: args{
envStrings: []string{
@ -332,7 +332,7 @@ func Test_env_load(t *testing.T) {
{
name: "with prefixes",
fields: fields{
prefixes: []string{"KRATOS_", "FOO"},
prefixs: []string{"KRATOS_", "FOO"},
},
args: args{
envStrings: []string{
@ -351,7 +351,7 @@ func Test_env_load(t *testing.T) {
{
name: "should not panic #1",
fields: fields{
prefixes: []string{"FOO"},
prefixs: []string{"FOO"},
},
args: args{
envStrings: []string{
@ -364,7 +364,7 @@ func Test_env_load(t *testing.T) {
{
name: "should not panic #2",
fields: fields{
prefixes: []string{"FOO=1"},
prefixs: []string{"FOO=1"},
},
args: args{
envStrings: []string{
@ -377,7 +377,7 @@ func Test_env_load(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := &env{
prefixes: tt.fields.prefixes,
prefixs: tt.fields.prefixs,
}
got := e.load(tt.args.envStrings)
if !reflect.DeepEqual(tt.want, got) {
@ -417,13 +417,3 @@ func Test_matchPrefix(t *testing.T) {
})
}
}
func Test_env_watch(t *testing.T) {
prefixes := []string{"BAR", "FOO"}
source := NewSource(prefixes...)
w, err := source.Watch()
if err != nil {
t.Errorf("expect no err, got %v", err)
}
_ = w.Stop()
}

@ -6,13 +6,13 @@ import (
"github.com/go-kratos/kratos/v2/config"
)
var _ config.Watcher = (*watcher)(nil)
type watcher struct {
ctx context.Context
cancel context.CancelFunc
}
var _ config.Watcher = (*watcher)(nil)
func NewWatcher() (config.Watcher, error) {
ctx, cancel := context.WithCancel(context.Background())
return &watcher{ctx: ctx, cancel: cancel}, nil

@ -121,10 +121,10 @@ func testWatchFile(t *testing.T, path string) {
}
kvs, err := watch.Next()
if err != nil {
t.Errorf("watch.Next() error(%v)", err)
t.Errorf(`watch.Next() error(%v)`, err)
}
if !reflect.DeepEqual(string(kvs[0].Value), _testJSONUpdate) {
t.Errorf("string(kvs[0].Value(%v) is not equal to _testJSONUpdate(%v)", kvs[0].Value, _testJSONUpdate)
t.Errorf(`string(kvs[0].Value(%v) is not equal to _testJSONUpdate(%v)`, kvs[0].Value, _testJSONUpdate)
}
newFilepath := filepath.Join(filepath.Dir(path), "test1.json")
@ -133,15 +133,15 @@ func testWatchFile(t *testing.T, path string) {
}
kvs, err = watch.Next()
if err == nil {
t.Errorf("watch.Next() error(%v)", err)
t.Errorf(`watch.Next() error(%v)`, err)
}
if kvs != nil {
t.Errorf("watch.Next() error(%v)", err)
t.Errorf(`watch.Next() error(%v)`, err)
}
err = watch.Stop()
if err != nil {
t.Errorf("watch.Stop() error(%v)", err)
t.Errorf(`watch.Stop() error(%v)`, err)
}
if err := os.Rename(newFilepath, path); err != nil {
@ -171,10 +171,10 @@ func testWatchDir(t *testing.T, path, file string) {
kvs, err := watch.Next()
if err != nil {
t.Errorf("watch.Next() error(%v)", err)
t.Errorf(`watch.Next() error(%v)`, err)
}
if !reflect.DeepEqual(string(kvs[0].Value), _testJSONUpdate) {
t.Errorf("string(kvs[0].Value(%s) is not equal to _testJSONUpdate(%v)", kvs[0].Value, _testJSONUpdate)
t.Errorf(`string(kvs[0].Value(%v) is not equal to _testJSONUpdate(%v)`, kvs[0].Value, _testJSONUpdate)
}
}
@ -208,7 +208,7 @@ func TestConfig(t *testing.T) {
func testConfig(t *testing.T, c config.Config) {
expected := map[string]interface{}{
"test.settings.int_key": int64(1000),
"test.settings.float_key": 1000.1,
"test.settings.float_key": float64(1000.1),
"test.settings.string_key": "string_value",
"test.settings.duration_key": time.Duration(10000),
"test.server.addr": "127.0.0.1",

@ -1,43 +0,0 @@
package file
import (
"testing"
)
func TestFormat(t *testing.T) {
tests := []struct {
input string
expect string
}{
{
input: "",
expect: "",
},
{
input: " ",
expect: "",
},
{
input: ".",
expect: "",
},
{
input: "a.",
expect: "",
},
{
input: ".b",
expect: "b",
},
{
input: "a.b",
expect: "b",
},
}
for _, v := range tests {
content := format(v.input)
if got, want := content, v.expect; got != want {
t.Errorf("expect %v,got %v", want, got)
}
}
}

@ -6,12 +6,9 @@ import (
"path/filepath"
"github.com/fsnotify/fsnotify"
"github.com/go-kratos/kratos/v2/config"
)
var _ config.Watcher = (*watcher)(nil)
type watcher struct {
f *file
fw *fsnotify.Watcher
@ -20,6 +17,8 @@ type watcher struct {
cancel context.CancelFunc
}
var _ config.Watcher = (*watcher)(nil)
func newWatcher(f *file) (config.Watcher, error) {
fw, err := fsnotify.NewWatcher()
if err != nil {

@ -51,7 +51,7 @@ func WithResolver(r Resolver) Option {
// WithLogger with config logger.
// Deprecated: use global logger instead.
func WithLogger(_ log.Logger) Option {
func WithLogger(l log.Logger) Option {
return func(o *options) {}
}

@ -2,7 +2,6 @@ package config
import (
"reflect"
"strings"
"testing"
)
@ -15,7 +14,7 @@ func TestDefaultDecoder(t *testing.T) {
target := make(map[string]interface{})
err := defaultDecoder(src, target)
if err != nil {
t.Fatal(err)
t.Fatal("err is not nil")
}
if !reflect.DeepEqual(target, map[string]interface{}{"service": []byte("config")}) {
t.Fatal(`target is not equal to map[string]interface{}{"service": "config"}`)
@ -29,7 +28,7 @@ func TestDefaultDecoder(t *testing.T) {
target = make(map[string]interface{})
err = defaultDecoder(src, target)
if err != nil {
t.Fatal(err)
t.Fatal("err is not nil")
}
if !reflect.DeepEqual(map[string]interface{}{
"service": map[string]interface{}{
@ -150,7 +149,7 @@ func TestDefaultResolver(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
err := defaultResolver(data)
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
rd := reader{
values: data,
@ -161,25 +160,25 @@ func TestDefaultResolver(t *testing.T) {
case int:
if actual, err = v.Int(); err == nil {
if !reflect.DeepEqual(test.expect.(int), int(actual.(int64))) {
t.Fatal("expect is not equal to actual")
t.Fatal(`expect is not equal to actual`)
}
}
case string:
if actual, err = v.String(); err == nil {
if !reflect.DeepEqual(test.expect, actual) {
t.Fatal("expect is not equal to actual")
t.Fatal(`expect is not equal to actual`)
}
}
case bool:
if actual, err = v.Bool(); err == nil {
if !reflect.DeepEqual(test.expect, actual) {
t.Fatal("expect is not equal to actual")
t.Fatal(`expect is not equal to actual`)
}
}
case float64:
if actual, err = v.Float(); err == nil {
if !reflect.DeepEqual(test.expect, actual) {
t.Fatal("expect is not equal to actual")
t.Fatal(`expect is not equal to actual`)
}
}
default:
@ -198,31 +197,3 @@ func TestDefaultResolver(t *testing.T) {
})
}
}
func TestExpand(t *testing.T) {
tests := []struct {
input string
mapping func(string) string
want string
}{
{
input: "${a}",
mapping: func(s string) string {
return strings.ToUpper(s)
},
want: "A",
},
{
input: "a",
mapping: func(s string) string {
return strings.ToUpper(s)
},
want: "a",
},
}
for _, tt := range tests {
if got := expand(tt.input, tt.mapping); got != tt.want {
t.Errorf("expand() want: %s, got: %s", tt.want, got)
}
}
}

@ -8,11 +8,11 @@ import (
"strings"
"sync"
"github.com/go-kratos/kratos/v2/log"
"github.com/imdario/mergo"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"github.com/go-kratos/kratos/v2/log"
)
// Reader is config reader.
@ -38,7 +38,9 @@ func newReader(opts options) Reader {
}
func (r *reader) Merge(kvs ...*KeyValue) error {
merged, err := r.cloneMap()
r.lock.Lock()
merged, err := cloneMap(r.values)
r.lock.Unlock()
if err != nil {
return err
}
@ -77,12 +79,6 @@ func (r *reader) Resolve() error {
return r.opts.resolver(r.values)
}
func (r *reader) cloneMap() (map[string]interface{}, error) {
r.lock.Lock()
defer r.lock.Unlock()
return cloneMap(r.values)
}
func cloneMap(src map[string]interface{}) (map[string]interface{}, error) {
// https://gist.github.com/soroushjp/0ec92102641ddfc3ad5515ca76405f4d
var buf bytes.Buffer
@ -94,12 +90,12 @@ func cloneMap(src map[string]interface{}) (map[string]interface{}, error) {
if err != nil {
return nil, err
}
var clone map[string]interface{}
err = dec.Decode(&clone)
var copy map[string]interface{}
err = dec.Decode(&copy)
if err != nil {
return nil, err
}
return clone, nil
return copy, nil
}
func convertMap(src interface{}) interface{} {

@ -29,7 +29,7 @@ func TestReader_Merge(t *testing.T) {
Format: "json",
})
if err == nil {
t.Fatal("err is nil")
t.Fatal(`err is nil`)
}
err = r.Merge(&KeyValue{
@ -38,15 +38,15 @@ func TestReader_Merge(t *testing.T) {
Format: "json",
})
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
vv, ok := r.Value("nice")
if !ok {
t.Fatal("ok is false")
t.Fatal(`ok is false`)
}
vvv, err := vv.String()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if vvv != "boat" {
t.Fatal(`vvv is not equal to "boat"`)
@ -58,18 +58,18 @@ func TestReader_Merge(t *testing.T) {
Format: "json",
})
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
vv, ok = r.Value("x")
if !ok {
t.Fatal("ok is false")
t.Fatal(`ok is false`)
}
vvx, err := vv.Int()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if vvx != 2 {
t.Fatal("vvx is not equal to 2")
if int64(2) != vvx {
t.Fatal(`vvx is not equal to 2`)
}
}
@ -118,57 +118,63 @@ a:
r := newReader(opts)
err := r.Merge(&test.kv)
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
vv, ok := r.Value("a.b.X")
if !ok {
t.Fatal("ok is false")
t.Fatal(`ok is false`)
}
vvv, err := vv.Int()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if int64(1) != vvv {
t.Fatal("vvv is not equal to 1")
t.Fatal(`vvv is not equal to 1`)
}
if err != nil {
t.Fatal(`err is not nil`)
}
vv, ok = r.Value("a.b.Y")
if !ok {
t.Fatal("ok is false")
t.Fatal(`ok is false`)
}
vvy, err := vv.String()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if vvy != "lol" {
t.Fatal(`vvy is not equal to "lol"`)
}
if err != nil {
t.Fatal(`err is not nil`)
}
vv, ok = r.Value("a.b.z")
if !ok {
t.Fatal("ok is false")
t.Fatal(`ok is false`)
}
vvz, err := vv.Bool()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if !vvz {
t.Fatal("vvz is not equal to true")
t.Fatal(`vvz is not equal to true`)
}
_, ok = r.Value("aasasdg=234l.asdfk,")
if ok {
t.Fatal("ok is true")
t.Fatal(`ok is true`)
}
_, ok = r.Value("aas......asdg=234l.asdfk,")
if ok {
t.Fatal("ok is true")
t.Fatal(`ok is true`)
}
_, ok = r.Value("a.b.Y.")
if ok {
t.Fatal("ok is true")
t.Fatal(`ok is true`)
}
})
}
@ -192,128 +198,13 @@ func TestReader_Source(t *testing.T) {
Format: "json",
})
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
b, err := r.Source()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if !reflect.DeepEqual([]byte(`{"a":{"b":{"X":1}}}`), b) {
t.Fatal("[]byte(`{\"a\":{\"b\":{\"X\":1}}}`) is not equal to b")
}
}
func TestCloneMap(t *testing.T) {
tests := []struct {
input map[string]interface{}
want map[string]interface{}
}{
{
input: map[string]interface{}{
"a": 1,
"b": "2",
"c": true,
},
want: map[string]interface{}{
"a": 1,
"b": "2",
"c": true,
},
},
{
input: map[string]interface{}{},
want: map[string]interface{}{},
},
{
input: nil,
want: map[string]interface{}{},
},
}
for _, tt := range tests {
if got, err := cloneMap(tt.input); err != nil {
t.Errorf("expect no err, got %v", err)
} else if !reflect.DeepEqual(got, tt.want) {
t.Errorf("cloneMap(%v) = %v, want %v", tt.input, got, tt.want)
}
}
}
func TestConvertMap(t *testing.T) {
tests := []struct {
input interface{}
want interface{}
}{
{
input: map[string]interface{}{
"a": 1,
"b": "2",
"c": true,
"d": []byte{65, 66, 67},
},
want: map[string]interface{}{
"a": 1,
"b": "2",
"c": true,
"d": "ABC",
},
},
{
input: []interface{}{1, 2.0, "3", true, nil, []interface{}{1, 2.0, "3", true, nil}},
want: []interface{}{1, 2.0, "3", true, nil, []interface{}{1, 2.0, "3", true, nil}},
},
{
input: []byte{65, 66, 67},
want: "ABC",
},
}
for _, tt := range tests {
if got := convertMap(tt.input); !reflect.DeepEqual(got, tt.want) {
t.Errorf("convertMap(%v) = %v, want %v", tt.input, got, tt.want)
}
}
}
func TestReadValue(t *testing.T) {
m := map[string]interface{}{
"a": 1,
"b": map[string]interface{}{
"c": "3",
"d": map[string]interface{}{
"e": true,
},
},
}
va := atomicValue{}
va.Store(1)
vbc := atomicValue{}
vbc.Store("3")
vbde := atomicValue{}
vbde.Store(true)
tests := []struct {
path string
want atomicValue
}{
{
path: "a",
want: va,
},
{
path: "b.c",
want: vbc,
},
{
path: "b.d.e",
want: vbde,
},
}
for _, tt := range tests {
if got, found := readValue(m, tt.path); !found {
t.Errorf("expect found %v in %v, but not.", tt.path, m)
} else if got.Load() != tt.want.Load() {
t.Errorf("readValue(%v, %v) = %v, want %v", m, tt.path, got, tt.want)
}
}
}

@ -1,7 +1,7 @@
package config
import (
"encoding/json"
stdjson "encoding/json"
"fmt"
"reflect"
"strconv"
@ -10,7 +10,7 @@ import (
"google.golang.org/protobuf/proto"
kratosjson "github.com/go-kratos/kratos/v2/encoding/json"
"github.com/go-kratos/kratos/v2/encoding/json"
)
var (
@ -36,10 +36,6 @@ type atomicValue struct {
atomic.Value
}
func (v *atomicValue) typeAssertError() error {
return fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
}
func (v *atomicValue) Bool() (bool, error) {
switch val := v.Load().(type) {
case bool:
@ -47,7 +43,7 @@ func (v *atomicValue) Bool() (bool, error) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, string:
return strconv.ParseBool(fmt.Sprint(val))
}
return false, v.typeAssertError()
return false, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
}
func (v *atomicValue) Int() (int64, error) {
@ -77,37 +73,35 @@ func (v *atomicValue) Int() (int64, error) {
case float64:
return int64(val), nil
case string:
return strconv.ParseInt(val, 10, 64)
return strconv.ParseInt(val, 10, 64) //nolint:gomnd
}
return 0, v.typeAssertError()
return 0, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
}
func (v *atomicValue) Slice() ([]Value, error) {
vals, ok := v.Load().([]interface{})
if !ok {
return nil, v.typeAssertError()
}
slices := make([]Value, 0, len(vals))
if vals, ok := v.Load().([]interface{}); ok {
var slices []Value
for _, val := range vals {
a := new(atomicValue)
a := &atomicValue{}
a.Store(val)
slices = append(slices, a)
}
return slices, nil
}
return nil, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
}
func (v *atomicValue) Map() (map[string]Value, error) {
vals, ok := v.Load().(map[string]interface{})
if !ok {
return nil, v.typeAssertError()
}
m := make(map[string]Value, len(vals))
if vals, ok := v.Load().(map[string]interface{}); ok {
m := make(map[string]Value)
for key, val := range vals {
a := new(atomicValue)
a := &atomicValue{}
a.Store(val)
m[key] = a
}
return m, nil
}
return nil, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
}
func (v *atomicValue) Float() (float64, error) {
@ -137,9 +131,9 @@ func (v *atomicValue) Float() (float64, error) {
case float64:
return val, nil
case string:
return strconv.ParseFloat(val, 64)
return strconv.ParseFloat(val, 64) //nolint:gomnd
}
return 0.0, v.typeAssertError()
return 0.0, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
}
func (v *atomicValue) String() (string, error) {
@ -150,10 +144,12 @@ func (v *atomicValue) String() (string, error) {
return fmt.Sprint(val), nil
case []byte:
return string(val), nil
case fmt.Stringer:
return val.String(), nil
default:
if s, ok := val.(fmt.Stringer); ok {
return s.String(), nil
}
}
return "", v.typeAssertError()
return "", fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load()))
}
func (v *atomicValue) Duration() (time.Duration, error) {
@ -165,14 +161,14 @@ func (v *atomicValue) Duration() (time.Duration, error) {
}
func (v *atomicValue) Scan(obj interface{}) error {
data, err := json.Marshal(v.Load())
data, err := stdjson.Marshal(v.Load())
if err != nil {
return err
}
if pb, ok := obj.(proto.Message); ok {
return kratosjson.UnmarshalOptions.Unmarshal(data, pb)
return json.UnmarshalOptions.Unmarshal(data, pb)
}
return json.Unmarshal(data, obj)
return stdjson.Unmarshal(data, obj)
}
type errValue struct {

@ -6,17 +6,17 @@ import (
"time"
)
func TestAtomicValue_Bool(t *testing.T) {
func Test_atomicValue_Bool(t *testing.T) {
vlist := []interface{}{"1", "t", "T", "true", "TRUE", "True", true, 1, int32(1)}
for _, x := range vlist {
v := atomicValue{}
v.Store(x)
b, err := v.Bool()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if !b {
t.Fatal("b is not equal to true")
t.Fatal(`b is not equal to true`)
}
}
@ -26,10 +26,10 @@ func TestAtomicValue_Bool(t *testing.T) {
v.Store(x)
b, err := v.Bool()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b {
t.Fatal("b is not equal to false")
t.Fatal(`b is not equal to false`)
}
}
@ -39,22 +39,22 @@ func TestAtomicValue_Bool(t *testing.T) {
v.Store(x)
_, err := v.Bool()
if err == nil {
t.Fatal("err is nil")
t.Fatal(`err is nil`)
}
}
}
func TestAtomicValue_Int(t *testing.T) {
func Test_atomicValue_Int(t *testing.T) {
vlist := []interface{}{"123123", float64(123123), int64(123123), int32(123123), 123123}
for _, x := range vlist {
v := atomicValue{}
v.Store(x)
b, err := v.Int()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b != 123123 {
t.Fatal("b is not equal to 123123")
t.Fatal(`b is not equal to 123123`)
}
}
@ -64,22 +64,22 @@ func TestAtomicValue_Int(t *testing.T) {
v.Store(x)
_, err := v.Int()
if err == nil {
t.Fatal("err is nil")
t.Fatal(`err is nil`)
}
}
}
func TestAtomicValue_Float(t *testing.T) {
vlist := []interface{}{"123123.1", 123123.1}
func Test_atomicValue_Float(t *testing.T) {
vlist := []interface{}{"123123.1", float64(123123.1)}
for _, x := range vlist {
v := atomicValue{}
v.Store(x)
b, err := v.Float()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b != 123123.1 {
t.Fatal("b is not equal to 123123.1")
if b != float64(123123.1) {
t.Fatal(`b is not equal to 123123.1`)
}
}
@ -89,7 +89,7 @@ func TestAtomicValue_Float(t *testing.T) {
v.Store(x)
_, err := v.Float()
if err == nil {
t.Fatal("err is nil")
t.Fatal(`err is nil`)
}
}
}
@ -103,17 +103,17 @@ func (t ts) String() string {
return fmt.Sprintf("%s%d", t.Name, t.Age)
}
func TestAtomicValue_String(t *testing.T) {
func Test_atomicValue_String(t *testing.T) {
vlist := []interface{}{"1", float64(1), int64(1), 1, int64(1)}
for _, x := range vlist {
v := atomicValue{}
v.Store(x)
b, err := v.String()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b != "1" {
t.Fatal("b is not equal to 1")
t.Fatal(`b is not equal to 1`)
}
}
@ -121,7 +121,7 @@ func TestAtomicValue_String(t *testing.T) {
v.Store(true)
b, err := v.String()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b != "true" {
t.Fatal(`b is not equal to "true"`)
@ -134,48 +134,48 @@ func TestAtomicValue_String(t *testing.T) {
})
b, err = v.String()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b != "test10" {
t.Fatal(`b is not equal to "test10"`)
}
}
func TestAtomicValue_Duration(t *testing.T) {
func Test_atomicValue_Duration(t *testing.T) {
vlist := []interface{}{int64(5)}
for _, x := range vlist {
v := atomicValue{}
v.Store(x)
b, err := v.Duration()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b != time.Duration(5) {
t.Fatal("b is not equal to time.Duration(5)")
t.Fatal(`b is not equal to time.Duration(5)`)
}
}
}
func TestAtomicValue_Slice(t *testing.T) {
func Test_atomicValue_Slice(t *testing.T) {
vlist := []interface{}{int64(5)}
v := atomicValue{}
v.Store(vlist)
slices, err := v.Slice()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
for _, v := range slices {
b, err := v.Duration()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b != time.Duration(5) {
t.Fatal("b is not equal to time.Duration(5)")
t.Fatal(`b is not equal to time.Duration(5)`)
}
}
}
func TestAtomicValue_Map(t *testing.T) {
func Test_atomicValue_Map(t *testing.T) {
vlist := make(map[string]interface{})
vlist["5"] = int64(5)
vlist["text"] = "text"
@ -183,21 +183,21 @@ func TestAtomicValue_Map(t *testing.T) {
v.Store(vlist)
m, err := v.Map()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
for k, v := range m {
if k == "5" {
b, err := v.Duration()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b != time.Duration(5) {
t.Fatal("b is not equal to time.Duration(5)")
t.Fatal(`b is not equal to time.Duration(5)`)
}
} else {
b, err := v.String()
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
if b != "text" {
t.Fatal(`b is not equal to "text"`)
@ -206,19 +206,20 @@ func TestAtomicValue_Map(t *testing.T) {
}
}
func TestAtomicValue_Scan(t *testing.T) {
func Test_atomicValue_Scan(t *testing.T) {
var err error
v := atomicValue{}
err := v.Scan(&struct {
err = v.Scan(&struct {
A string `json:"a"`
}{"a"})
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
err = v.Scan(&struct {
A string `json:"a"`
}{"a"})
if err != nil {
t.Fatal(err)
t.Fatal(`err is not nil`)
}
}

@ -97,4 +97,5 @@ config := map[string]interface{}{
}
}
}
_ = config
```

@ -3,14 +3,12 @@ package apollo
import (
"strings"
"github.com/apolloconfig/agollo/v4"
"github.com/apolloconfig/agollo/v4/constant"
apolloconfig "github.com/apolloconfig/agollo/v4/env/config"
"github.com/apolloconfig/agollo/v4/extension"
"github.com/go-kratos/kratos/v2/config"
"github.com/go-kratos/kratos/v2/encoding"
"github.com/go-kratos/kratos/v2/log"
"github.com/apolloconfig/agollo/v4"
apolloConfig "github.com/apolloconfig/agollo/v4/env/config"
)
type apollo struct {
@ -18,15 +16,6 @@ type apollo struct {
opt *options
}
const (
yaml = "yaml"
yml = "yml"
json = "json"
properties = "properties"
)
var formats map[string]struct{}
// Option is apollo option
type Option func(*options)
@ -38,7 +27,6 @@ type options struct {
namespace string
isBackupConfig bool
backupPath string
originConfig bool
}
// WithAppID with apollo config app id
@ -97,23 +85,13 @@ func WithBackupPath(backupPath string) Option {
}
}
// WithOriginalConfig use the original configuration file without parse processing
func WithOriginalConfig() Option {
return func(o *options) {
extension.AddFormatParser(constant.JSON, &jsonExtParser{})
extension.AddFormatParser(constant.YAML, &yamlExtParser{})
extension.AddFormatParser(constant.YML, &yamlExtParser{})
o.originConfig = true
}
}
func NewSource(opts ...Option) config.Source {
op := options{}
for _, o := range opts {
o(&op)
}
client, err := agollo.StartWithConfig(func() (*apolloconfig.AppConfig, error) {
return &apolloconfig.AppConfig{
client, err := agollo.StartWithConfig(func() (*apolloConfig.AppConfig, error) {
return &apolloConfig.AppConfig{
AppID: op.appid,
Cluster: op.cluster,
NamespaceName: op.namespace,
@ -126,100 +104,26 @@ func NewSource(opts ...Option) config.Source {
if err != nil {
panic(err)
}
return &apollo{client: client, opt: &op}
}
func format(ns string) string {
// genKey got the key of config.KeyValue pair.
// eg: namespace.ext with subKey got namespace.subKey
func genKey(ns, sub string) string {
arr := strings.Split(ns, ".")
suffix := arr[len(arr)-1]
if len(arr) <= 1 || suffix == properties {
return json
}
if _, ok := formats[suffix]; !ok {
// fallback
return json
}
return suffix
}
func (e *apollo) load() []*config.KeyValue {
kvs := make([]*config.KeyValue, 0)
namespaces := strings.Split(e.opt.namespace, ",")
for _, ns := range namespaces {
if !e.opt.originConfig {
kv, err := e.getConfig(ns)
if err != nil {
log.Errorf("apollo get config failed,err:%v", err)
continue
}
kvs = append(kvs, kv)
continue
}
if strings.Contains(ns, ".") && !strings.HasSuffix(ns, "."+properties) &&
(format(ns) == yaml || format(ns) == yml || format(ns) == json) {
kv, err := e.getOriginConfig(ns)
if err != nil {
log.Errorf("apollo get config failed,err:%v", err)
continue
}
kvs = append(kvs, kv)
continue
}
kv, err := e.getConfig(ns)
if err != nil {
log.Errorf("apollo get config failed,err:%v", err)
continue
}
kvs = append(kvs, kv)
if len(arr) < 1 {
return sub
}
return kvs
}
func (e *apollo) getConfig(ns string) (*config.KeyValue, error) {
next := map[string]interface{}{}
e.client.GetConfigCache(ns).Range(func(key, value interface{}) bool {
// all values are out properties format
resolve(genKey(ns, key.(string)), value, next)
return true
})
f := format(ns)
codec := encoding.GetCodec(f)
val, err := codec.Marshal(next)
if err != nil {
return nil, err
if len(arr) == 1 {
if ns == "" {
return sub
}
return &config.KeyValue{
Key: ns,
Value: val,
Format: f,
}, nil
}
func (e apollo) getOriginConfig(ns string) (*config.KeyValue, error) {
value, err := e.client.GetConfigCache(ns).Get("content")
if err != nil {
return nil, err
return ns + "." + sub
}
// serialize the namespace content KeyValue into bytes.
return &config.KeyValue{
Key: ns,
Value: []byte(value.(string)),
Format: format(ns),
}, nil
}
func (e *apollo) Load() (kv []*config.KeyValue, err error) {
return e.load(), nil
}
func (e *apollo) Watch() (config.Watcher, error) {
w, err := newWatcher(e)
if err != nil {
return nil, err
}
return w, nil
return strings.Join(arr[:len(arr)-1], ".") + "." + sub
}
// resolve convert kv pair into one map[string]interface{} by split key into different
@ -255,32 +159,54 @@ func resolve(key string, value interface{}, target map[string]interface{}) {
}
}
// genKey got the key of config.KeyValue pair.
// eg: namespace.ext with subKey got namespace.subKey
func genKey(ns, sub string) string {
func format(ns string) string {
arr := strings.Split(ns, ".")
if len(arr) == 1 {
if ns == "" {
return sub
if len(arr) <= 1 {
return "json"
}
return ns + "." + sub
return arr[len(arr)-1]
}
func (e *apollo) load() []*config.KeyValue {
kv := make([]*config.KeyValue, 0)
namespaces := strings.Split(e.opt.namespace, ",")
for _, ns := range namespaces {
next := map[string]interface{}{}
e.client.GetConfigCache(ns).Range(func(key, value interface{}) bool {
// all values are out properties format
resolve(genKey(ns, key.(string)), value, next)
return true
})
// serialize the namespace content KeyValue into bytes.
f := format(ns)
codec := encoding.GetCodec(f)
val, err := codec.Marshal(next)
if err != nil {
log.Warnf("apollo could not handle namespace %s: %v", ns, err)
continue
}
suffix := arr[len(arr)-1]
_, ok := formats[suffix]
if ok {
return strings.Join(arr[:len(arr)-1], ".") + "." + sub
kv = append(kv, &config.KeyValue{
Key: ns,
Value: val,
Format: f,
})
}
return ns + "." + sub
return kv
}
func init() {
formats = make(map[string]struct{})
func (e *apollo) Load() (kv []*config.KeyValue, err error) {
return e.load(), nil
}
formats[yaml] = struct{}{}
formats[yml] = struct{}{}
formats[json] = struct{}{}
formats[properties] = struct{}{}
func (e *apollo) Watch() (config.Watcher, error) {
w, err := newWatcher(e)
if err != nil {
return nil, err
}
return w, nil
}

@ -1,6 +1,7 @@
package apollo
import (
"reflect"
"testing"
)
@ -15,28 +16,44 @@ func Test_genKey(t *testing.T) {
want string
}{
{
name: "blank namespace",
name: "case 1",
args: args{
ns: "",
sub: "x.y",
sub: "has_no_ns",
},
want: "x.y",
want: "has_no_ns",
},
{
name: "properties namespace",
name: "case 2",
args: args{
ns: "application",
sub: "x.y",
ns: "ns.ext",
sub: "sub",
},
want: "application.x.y",
want: "ns.sub",
},
{
name: "namespace with format",
name: "case 3",
args: args{
ns: "app.yaml",
sub: "x.y",
ns: "",
sub: "",
},
want: "",
},
{
name: "case 4",
args: args{
ns: "ns.ext",
sub: "sub.sub2.sub3",
},
want: "ns.sub.sub2.sub3",
},
want: "app.x.y",
{
name: "case 5",
args: args{
ns: "ns.more.ext",
sub: "sub.sub2.sub3",
},
want: "ns.more.sub.sub2.sub3",
},
}
for _, tt := range tests {
@ -49,42 +66,124 @@ func Test_genKey(t *testing.T) {
}
func Test_format(t *testing.T) {
type args struct {
ns string
}
tests := []struct {
name string
namespace string
args args
want string
}{
{
name: "properties namespace",
namespace: "application",
want: "json",
name: "case 0",
args: args{
ns: "ns.yaml",
},
want: "yaml",
},
{
name: "properties namespace #1",
namespace: "app.setting",
name: "case 1",
args: args{
ns: "ns",
},
want: "json",
},
{
name: "namespace with format[yaml]",
namespace: "app.yaml",
want: "yaml",
name: "case 2",
args: args{
ns: "ns.more.json",
},
{
name: "namespace with format[yml]",
namespace: "app.yml",
want: "yml",
want: "json",
},
{
name: "namespace with format[json]",
namespace: "app.json",
name: "case 3",
args: args{
ns: "",
},
want: "json",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := format(tt.namespace); got != tt.want {
if got := format(tt.args.ns); got != tt.want {
t.Errorf("format() = %v, want %v", got, tt.want)
}
})
}
}
func Test_convertProperties(t *testing.T) {
type args struct {
key string
value interface{}
target map[string]interface{}
}
tests := []struct {
name string
args args
want map[string]interface{}
}{
{
name: "case 0",
args: args{
key: "application.name",
value: "app name",
target: map[string]interface{}{},
},
want: map[string]interface{}{
"application": map[string]interface{}{
"name": "app name",
},
},
},
{
name: "case 1",
args: args{
key: "application",
value: []string{"1", "2", "3"},
target: map[string]interface{}{},
},
want: map[string]interface{}{
"application": []string{"1", "2", "3"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resolve(tt.args.key, tt.args.value, tt.args.target)
if !reflect.DeepEqual(tt.args.target, tt.want) {
t.Errorf("convertProperties() = %v, want %v", tt.args.target, tt.want)
}
})
}
}
func Test_convertProperties_duplicate(t *testing.T) {
target := map[string]interface{}{}
resolve("application.name", "name", target)
_, ok := target["application"]
if !reflect.DeepEqual(ok, true) {
t.Errorf("ok = %v, want %v", ok, true)
}
_, ok = target["application"].(map[string]interface{})["name"]
if !reflect.DeepEqual(ok, true) {
t.Errorf("ok = %v, want %v", ok, true)
}
if !reflect.DeepEqual(target["application"].(map[string]interface{})["name"], "name") {
t.Errorf("target[\"application\"][\"name\"] = %v, want %v", target["application"].(map[string]interface{})["name"], "name")
}
// cause duplicate, the oldest value will be kept
resolve("application.name.first", "first name", target)
_, ok = target["application"]
if !reflect.DeepEqual(ok, true) {
t.Errorf("ok = %v, want %v", ok, true)
}
_, ok = target["application"].(map[string]interface{})["name"]
if !reflect.DeepEqual(ok, true) {
t.Errorf("ok = %v, want %v", ok, true)
}
if !reflect.DeepEqual(target["application"].(map[string]interface{})["name"], "name") {
t.Errorf("target[\"application\"][\"name\"] = %v, want %v", target["application"].(map[string]interface{})["name"], "name")
}
}

@ -3,10 +3,13 @@ module github.com/go-kratos/kratos/contrib/config/apollo/v2
go 1.16
require (
github.com/apolloconfig/agollo/v4 v4.3.0
github.com/go-kratos/kratos/v2 v2.6.3
github.com/apolloconfig/agollo/v4 v4.2.0
github.com/go-kratos/kratos/v2 v2.3.0
)
require github.com/spf13/viper v1.11.0 // indirect
require (
github.com/spf13/viper v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1
)
replace github.com/go-kratos/kratos/v2 => ../../../

File diff suppressed because it is too large Load Diff

@ -1,13 +1,41 @@
package apollo
import (
"encoding/json"
"gopkg.in/yaml.v3"
"github.com/apolloconfig/agollo/v4/constant"
"github.com/apolloconfig/agollo/v4/extension"
)
type jsonExtParser struct{}
func (parser jsonExtParser) Parse(configContent interface{}) (map[string]interface{}, error) {
return map[string]interface{}{"content": configContent}, nil
v, ok := configContent.(string)
if !ok {
return nil, nil
}
out := make(map[string]interface{}, 4)
err := json.Unmarshal([]byte(v), &out)
return out, err
}
type yamlExtParser struct{}
func (parser yamlExtParser) Parse(configContent interface{}) (map[string]interface{}, error) {
return map[string]interface{}{"content": configContent}, nil
func (parser yamlExtParser) Parse(configContent interface{}) (out map[string]interface{}, err error) {
v, ok := configContent.(string)
if !ok {
return nil, nil
}
out = make(map[string]interface{}, 4)
err = yaml.Unmarshal([]byte(v), &out)
return
}
func init() {
// add json/yaml/yml format
extension.AddFormatParser(constant.JSON, &jsonExtParser{})
extension.AddFormatParser(constant.YAML, &yamlExtParser{})
extension.AddFormatParser(constant.YML, &yamlExtParser{})
}

@ -2,45 +2,25 @@ package apollo
import (
"context"
"strings"
"github.com/apolloconfig/agollo/v4/storage"
"github.com/go-kratos/kratos/v2/config"
"github.com/go-kratos/kratos/v2/encoding"
"github.com/go-kratos/kratos/v2/log"
"github.com/apolloconfig/agollo/v4/storage"
)
type watcher struct {
out <-chan []*config.KeyValue
ctx context.Context
cancelFn func()
}
type customChangeListener struct {
in chan<- []*config.KeyValue
apollo *apollo
}
func (c *customChangeListener) onChange(namespace string, changes map[string]*storage.ConfigChange) []*config.KeyValue {
kv := make([]*config.KeyValue, 0, 2)
if strings.Contains(namespace, ".") && !strings.HasSuffix(namespace, "."+properties) &&
(format(namespace) == yaml || format(namespace) == yml || format(namespace) == json) {
value, err := c.apollo.client.GetConfigCache(namespace).Get("content")
if err != nil {
log.Warnw("apollo get config failed", "err", err)
return nil
}
kv = append(kv, &config.KeyValue{
Key: namespace,
Value: []byte(value.(string)),
Format: format(namespace),
})
return kv
}
next := make(map[string]interface{})
for key, change := range changes {
@ -72,38 +52,34 @@ func (c *customChangeListener) OnChange(changeEvent *storage.ChangeEvent) {
c.in <- change
}
func (c *customChangeListener) OnNewestChange(_ *storage.FullChangeEvent) {}
func (c *customChangeListener) OnNewestChange(changeEvent *storage.FullChangeEvent) {}
func newWatcher(a *apollo) (config.Watcher, error) {
changeCh := make(chan []*config.KeyValue)
listener := &customChangeListener{in: changeCh, apollo: a}
listener := &customChangeListener{in: changeCh}
a.client.AddChangeListener(listener)
ctx, cancel := context.WithCancel(context.Background())
return &watcher{
out: changeCh,
ctx: ctx,
cancelFn: func() {
a.client.RemoveChangeListener(listener)
cancel()
close(changeCh)
},
}, nil
}
// Next will be blocked until the Stop method is called
func (w *watcher) Next() ([]*config.KeyValue, error) {
select {
case kv := <-w.out:
return kv, nil
case <-w.ctx.Done():
return nil, w.ctx.Err()
kv, ok := <-w.out
if !ok {
return nil, context.Canceled
}
return kv, nil
}
func (w *watcher) Stop() error {
if w.cancelFn != nil {
w.cancelFn()
}
return nil
}

@ -2,12 +2,11 @@
```go
import (
"github.com/hashicorp/consul/api"
"github.com/go-kratos/kratos/contrib/config/consul/v2"
"github.com/hashicorp/consul/api"
)
func main() {
consulClient, err := api.NewClient(&api.Config{
Address: "127.0.0.1:8500",
})
@ -15,8 +14,8 @@ func main() {
panic(err)
}
cs, err := consul.New(consulClient, consul.WithPath("app/cart/configs/"))
// consul中需要标注文件后缀,kratos读取配置需要适配文件后缀
// The file suffix needs to be marked, and kratos needs to adapt the file suffix to read the configuration.
//consul中需要标注文件后缀,kratos读取配置需要适配文件后缀
//The file suffix needs to be marked, and kratos needs to adapt the file suffix to read the configuration.
if err != nil {
panic(err)
}

@ -6,9 +6,8 @@ import (
"path/filepath"
"strings"
"github.com/hashicorp/consul/api"
"github.com/go-kratos/kratos/v2/config"
"github.com/hashicorp/consul/api"
)
// Option is etcd config option.
@ -28,9 +27,9 @@ func WithContext(ctx context.Context) Option {
// WithPath is config path
func WithPath(p string) Option {
return func(o *options) {
return Option(func(o *options) {
o.path = p
}
})
}
type source struct {

@ -3,11 +3,8 @@ package consul
import (
"reflect"
"testing"
"time"
"github.com/hashicorp/consul/api"
"github.com/go-kratos/kratos/v2/config"
)
const testPath = "kratos/test/config"
@ -102,158 +99,3 @@ func TestExtToFormat(t *testing.T) {
t.Errorf("kvs[0].Format is %s", kvs[0].Format)
}
}
func Test_source_Watch(t *testing.T) {
client, err := api.NewClient(&api.Config{
Address: "127.0.0.1:8500",
})
if err != nil {
t.Fatal(err)
}
source, err := New(client, WithPath(testPath))
if err != nil {
t.Fatal(err)
}
type fields struct {
source config.Source
}
type args struct {
key string
value string
}
tests := []struct {
name string
fields fields
args args
want string
wantErr bool
deferFunc func(t *testing.T)
}{
{
name: "normal",
fields: fields{source: source},
args: args{
key: testKey,
value: "test value",
},
want: "test value",
wantErr: false,
deferFunc: func(t *testing.T) {
_, err := client.KV().Delete(testKey, nil)
if err != nil {
t.Error(err)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.deferFunc != nil {
defer tt.deferFunc(t)
}
got, err := tt.fields.source.Watch()
if (err != nil) != tt.wantErr {
t.Errorf("Watch() error = %v, wantErr %v", err, tt.wantErr)
return
}
time.Sleep(100 * time.Millisecond)
_, err = client.KV().Put(&api.KVPair{Key: tt.args.key, Value: []byte(tt.args.value)}, nil)
if err != nil {
t.Error(err)
}
next, err := got.Next()
if (err != nil) != tt.wantErr {
t.Errorf("Watch() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(next) != 1 {
t.Error("watch is error")
}
if !reflect.DeepEqual(string(next[0].Value), tt.want) {
t.Errorf("Watch got = %v, want %v", string(next[0].Value), tt.want)
}
})
}
}
func Test_source_Load(t *testing.T) {
client, err := api.NewClient(&api.Config{
Address: "127.0.0.1:8500",
})
if err != nil {
t.Fatal(err)
}
source, err := New(client, WithPath(testPath))
if err != nil {
t.Fatal(err)
}
type args struct {
key string
value string
}
type fields struct {
source config.Source
}
tests := []struct {
name string
args args
fields fields
want []*config.KeyValue
wantErr bool
deferFunc func(t *testing.T)
}{
{
name: "normal",
args: args{
key: testKey,
value: "test value",
},
fields: fields{
source: source,
},
want: []*config.KeyValue{
{
Key: "key",
Value: []byte("test value"),
},
},
deferFunc: func(t *testing.T) {
_, err1 := client.KV().Delete(testKey, nil)
if err1 != nil {
t.Error(err)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.deferFunc != nil {
defer tt.deferFunc(t)
}
_, err = client.KV().Put(&api.KVPair{Key: tt.args.key, Value: []byte(tt.args.value)}, nil)
if err != nil {
t.Error(err)
}
got, err := tt.fields.source.Load()
if (err != nil) != tt.wantErr {
t.Errorf("Load() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got[0], tt.want[0]) {
t.Errorf("Load() got = %v, want %v", got, tt.want)
}
})
}
}

@ -3,13 +3,8 @@ module github.com/go-kratos/kratos/contrib/config/consul/v2
go 1.15
require (
github.com/armon/go-metrics v0.3.10 // indirect
github.com/go-kratos/kratos/v2 v2.6.3
github.com/hashicorp/consul/api v1.20.0
github.com/hashicorp/go-hclog v0.14.1 // indirect
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
github.com/hashicorp/go-msgpack v0.5.5 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/go-kratos/kratos/v2 v2.3.0
github.com/hashicorp/consul/api v1.12.0
)
replace github.com/go-kratos/kratos/v2 => ../../../

File diff suppressed because it is too large Load Diff

@ -1,24 +1,19 @@
package consul
import (
"context"
"github.com/go-kratos/kratos/v2/config"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/api/watch"
"github.com/go-kratos/kratos/v2/config"
)
type watcher struct {
source *source
ch chan interface{}
closeChan chan struct{}
wp *watch.Plan
ctx context.Context
cancel context.CancelFunc
}
func (w *watcher) handle(_ uint64, data interface{}) {
func (w *watcher) handle(idx uint64, data interface{}) {
if data == nil {
return
}
@ -32,13 +27,10 @@ func (w *watcher) handle(_ uint64, data interface{}) {
}
func newWatcher(s *source) (*watcher, error) {
ctx, cancel := context.WithCancel(context.Background())
w := &watcher{
source: s,
ch: make(chan interface{}),
ctx: ctx,
cancel: cancel,
closeChan: make(chan struct{}),
}
wp, err := watch.Parse(map[string]interface{}{"type": "keyprefix", "prefix": s.options.path})
@ -62,15 +54,18 @@ func newWatcher(s *source) (*watcher, error) {
func (w *watcher) Next() ([]*config.KeyValue, error) {
select {
case <-w.ch:
case _, ok := <-w.ch:
if !ok {
return nil, nil
}
return w.source.Load()
case <-w.ctx.Done():
return nil, w.ctx.Err()
case <-w.closeChan:
return nil, nil
}
}
func (w *watcher) Stop() error {
w.wp.Stop()
w.cancel()
close(w.closeChan)
return nil
}

@ -4,14 +4,13 @@
import (
"log"
clientv3 "go.etcd.io/etcd/client/v3"
"google.golang.org/grpc"
cfg "github.com/go-kratos/kratos/contrib/config/etcd/v2"
"github.com/go-kratos/kratos/v2/config"
clientv3 "go.etcd.io/etcd/client/v3"
"google.golang.org/grpc"
)
// create an etcd client
// create a etcd client
client, err := clientv3.New(clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout: time.Second,
@ -31,16 +30,12 @@ if err != nil {
c := config.New(config.WithSource(source))
defer c.Close()
// load sources before get
if err := c.Load(); err != nil {
log.Fatalln(err)
}
// acquire config value
foo, err := c.Value("/app-config").String()
if err != nil {
log.Fatalln(err)
log.Println(err)
}
println(foo)
log.Println(foo)
```

@ -6,9 +6,8 @@ import (
"path/filepath"
"strings"
clientv3 "go.etcd.io/etcd/client/v3"
"github.com/go-kratos/kratos/v2/config"
clientv3 "go.etcd.io/etcd/client/v3"
)
// Option is etcd config option.

@ -15,8 +15,7 @@ const testKey = "/kratos/test/config"
func TestConfig(t *testing.T) {
client, err := clientv3.New(clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout: time.Second,
DialOptions: []grpc.DialOption{grpc.WithBlock()},
DialTimeout: time.Second, DialOptions: []grpc.DialOption{grpc.WithBlock()},
})
if err != nil {
t.Fatal(err)
@ -109,61 +108,3 @@ func TestExtToFormat(t *testing.T) {
t.Errorf("kvs[0].Format is %s", kvs[0].Format)
}
}
func TestEtcdWithPath(t *testing.T) {
tests := []struct {
name string
fields string
want string
}{
{
name: "default",
fields: testKey,
want: testKey,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
options := &options{
ctx: context.Background(),
}
got := WithPath(tt.fields)
got(options)
if options.path != tt.want {
t.Errorf("WithPath(tt.fields) = %v, want %v", got, tt.want)
}
})
}
}
func TestEtcdWithPrefix(t *testing.T) {
tests := []struct {
name string
fields bool
want bool
}{
{
name: "default",
fields: false,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
options := &options{
ctx: context.Background(),
}
got := WithPrefix(tt.fields)
got(options)
if options.prefix != tt.want {
t.Errorf("WithPrefix(tt.fields) = %v, want %v", got, tt.want)
}
})
}
}

@ -3,9 +3,9 @@ module github.com/go-kratos/kratos/contrib/config/etcd/v2
go 1.16
require (
github.com/go-kratos/kratos/v2 v2.6.3
go.etcd.io/etcd/client/v3 v3.5.8
google.golang.org/grpc v1.56.1
github.com/go-kratos/kratos/v2 v2.3.0
go.etcd.io/etcd/client/v3 v3.5.4
google.golang.org/grpc v1.46.2
)
replace github.com/go-kratos/kratos/v2 => ../../../

File diff suppressed because it is too large Load Diff

@ -1,27 +1,20 @@
package etcd
import (
"context"
clientv3 "go.etcd.io/etcd/client/v3"
"github.com/go-kratos/kratos/v2/config"
clientv3 "go.etcd.io/etcd/client/v3"
)
type watcher struct {
source *source
ch clientv3.WatchChan
ctx context.Context
cancel context.CancelFunc
closeChan chan struct{}
}
func newWatcher(s *source) *watcher {
ctx, cancel := context.WithCancel(context.Background())
w := &watcher{
source: s,
ctx: ctx,
cancel: cancel,
closeChan: make(chan struct{}),
}
var opts []clientv3.OpOption
@ -33,19 +26,19 @@ func newWatcher(s *source) *watcher {
return w
}
func (w *watcher) Next() ([]*config.KeyValue, error) {
func (s *watcher) Next() ([]*config.KeyValue, error) {
select {
case resp := <-w.ch:
if resp.Err() != nil {
return nil, resp.Err()
case _, ok := <-s.ch:
if !ok {
return nil, nil
}
return w.source.Load()
case <-w.ctx.Done():
return nil, w.ctx.Err()
return s.source.Load()
case <-s.closeChan:
return nil, nil
}
}
func (w *watcher) Stop() error {
w.cancel()
func (s *watcher) Stop() error {
close(s.closeChan)
return nil
}

@ -7,13 +7,12 @@ import (
"path/filepath"
"strings"
"github.com/go-kratos/kratos/v2/config"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"github.com/go-kratos/kratos/v2/config"
)
// Option is kubernetes option.

@ -1,44 +1,18 @@
package kubernetes
import (
"context"
"log"
"path/filepath"
"reflect"
"strings"
"testing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"github.com/go-kratos/kratos/v2/config"
)
const (
testKey = "test_config.json"
namespace = "default"
name = "test"
)
var (
keyPath = strings.Join([]string{namespace, name, testKey}, "/")
objectMeta = metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Labels: map[string]string{
"app": "test",
},
}
"k8s.io/client-go/util/homedir"
)
func TestSource(t *testing.T) {
home := homedir.HomeDir()
s := NewSource(
Namespace("default"),
Namespace("mesh"),
LabelSelector(""),
KubeConfig(filepath.Join(home, ".kube", "config")),
)
@ -66,143 +40,3 @@ func ExampleNewSource() {
log.Panic(err)
}
}
func TestConfig(t *testing.T) {
restConfig, err := rest.InClusterConfig()
home := homedir.HomeDir()
options := []Option{
Namespace(namespace),
LabelSelector("app=test"),
}
if err != nil {
kubeconfig := filepath.Join(home, ".kube", "config")
restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
t.Fatal(err)
}
options = append(options, KubeConfig(kubeconfig))
}
clientSet, err := kubernetes.NewForConfig(restConfig)
if err != nil {
t.Fatal(err)
}
clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace)
source := NewSource(options...)
if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{
ObjectMeta: objectMeta,
Data: map[string]string{
testKey: "test config",
},
}, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
defer func() {
if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil {
t.Error(err)
}
}()
kvs, err := source.Load()
if err != nil {
t.Fatal(err)
}
if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "test config" {
t.Fatal("config error")
}
w, err := source.Watch()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = w.Stop()
}()
// create also produce an event, discard it
if _, err = w.Next(); err != nil {
t.Fatal(err)
}
if _, err = clientSetConfigMaps.Update(context.Background(), &v1.ConfigMap{
ObjectMeta: objectMeta,
Data: map[string]string{
testKey: "new config",
},
}, metav1.UpdateOptions{}); err != nil {
t.Error(err)
}
if kvs, err = w.Next(); err != nil {
t.Fatal(err)
}
if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "new config" {
t.Fatal("config error")
}
}
func TestExtToFormat(t *testing.T) {
restConfig, err := rest.InClusterConfig()
home := homedir.HomeDir()
options := []Option{
Namespace(namespace),
LabelSelector("app=test"),
}
if err != nil {
kubeconfig := filepath.Join(home, ".kube", "config")
restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
t.Fatal(err)
}
options = append(options, KubeConfig(kubeconfig))
}
clientSet, err := kubernetes.NewForConfig(restConfig)
if err != nil {
t.Fatal(err)
}
clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace)
tc := `{"a":1}`
if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{
ObjectMeta: objectMeta,
Data: map[string]string{
testKey: tc,
},
}, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
defer func() {
if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil {
t.Error(err)
}
}()
source := NewSource(options...)
if err != nil {
t.Fatal(err)
}
kvs, err := source.Load()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(len(kvs), 1) {
t.Errorf("len(kvs) = %d", len(kvs))
}
if !reflect.DeepEqual(keyPath, kvs[0].Key) {
t.Errorf("kvs[0].Key is %s", kvs[0].Key)
}
if !reflect.DeepEqual(tc, string(kvs[0].Value)) {
t.Errorf("kvs[0].Value is %s", kvs[0].Value)
}
if !reflect.DeepEqual("json", kvs[0].Format) {
t.Errorf("kvs[0].Format is %s", kvs[0].Format)
}
}

@ -3,10 +3,10 @@ module github.com/go-kratos/kratos/contrib/config/kubernetes/v2
go 1.16
require (
github.com/go-kratos/kratos/v2 v2.6.3
k8s.io/api v0.26.3
k8s.io/apimachinery v0.26.3
k8s.io/client-go v0.26.3
github.com/go-kratos/kratos/v2 v2.3.0
k8s.io/api v0.24.1
k8s.io/apimachinery v0.24.1
k8s.io/client-go v0.24.1
)
replace github.com/go-kratos/kratos/v2 => ../../../

File diff suppressed because it is too large Load Diff

@ -4,11 +4,10 @@ import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/config"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"github.com/go-kratos/kratos/v2/config"
)
type watcher struct {

@ -2,10 +2,9 @@
```go
import (
kconfig "github.com/go-kratos/kratos/v2/config"
"github.com/nacos-group/nacos-sdk-go/clients"
"github.com/nacos-group/nacos-sdk-go/common/constant"
kconfig "github.com/go-kratos/kratos/v2/config"
)

@ -4,18 +4,28 @@ import (
"context"
"path/filepath"
"strings"
"time"
"github.com/go-kratos/kratos/v2/config"
"github.com/nacos-group/nacos-sdk-go/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/vo"
"github.com/go-kratos/kratos/v2/config"
)
type Option func(*options)
type options struct {
endpoint string // nolint:structcheck,unused
namespaceID string // nolint:structcheck,unused
group string
dataID string
timeoutMs uint64
logLevel string
logDir string
cacheDir string
}
// WithGroup With nacos config group.
@ -32,6 +42,34 @@ func WithDataID(dataID string) Option {
}
}
// WithLogDir With nacos config group.
func WithLogDir(logDir string) Option {
return func(o *options) {
o.logDir = logDir
}
}
// WithCacheDir With nacos config cache dir.
func WithCacheDir(cacheDir string) Option {
return func(o *options) {
o.cacheDir = cacheDir
}
}
// WithLogLevel With nacos config log level.
func WithLogLevel(logLevel string) Option {
return func(o *options) {
o.logLevel = logLevel
}
}
// WithTimeout With nacos config timeout.
func WithTimeout(time time.Duration) Option {
return func(o *options) {
o.timeoutMs = uint64(time.Milliseconds())
}
}
type Config struct {
opts options
client config_client.IConfigClient
@ -68,7 +106,7 @@ func (c *Config) Watch() (config.Watcher, error) {
err := c.client.ListenConfig(vo.ConfigParam{
DataId: c.opts.dataID,
Group: c.opts.group,
OnChange: func(_, group, dataId, data string) {
OnChange: func(namespace, group, dataId, data string) {
if dataId == watcher.dataID && group == watcher.group {
watcher.content <- data
}
@ -79,3 +117,56 @@ func (c *Config) Watch() (config.Watcher, error) {
}
return watcher, nil
}
type Watcher struct {
context.Context
dataID string
group string
content chan string
cancelListenConfig cancelListenConfigFunc
cancel context.CancelFunc
}
type cancelListenConfigFunc func(params vo.ConfigParam) (err error)
func newWatcher(ctx context.Context, dataID string, group string, cancelListenConfig cancelListenConfigFunc) *Watcher {
w := &Watcher{
dataID: dataID,
group: group,
cancelListenConfig: cancelListenConfig,
content: make(chan string, 100),
}
ctx, cancel := context.WithCancel(ctx)
w.Context = ctx
w.cancel = cancel
return w
}
func (w *Watcher) Next() ([]*config.KeyValue, error) {
select {
case <-w.Context.Done():
return nil, nil
case content := <-w.content:
k := w.dataID
return []*config.KeyValue{
{
Key: k,
Value: []byte(content),
Format: strings.TrimPrefix(filepath.Ext(k), "."),
},
}, nil
}
}
func (w *Watcher) Close() error {
err := w.cancelListenConfig(vo.ConfigParam{
DataId: w.dataID,
Group: w.group,
})
w.cancel()
return err
}
func (w *Watcher) Stop() error {
return w.Close()
}

@ -1,130 +1,41 @@
package config
import (
"reflect"
"fmt"
"net"
"testing"
"time"
"github.com/nacos-group/nacos-sdk-go/clients"
"github.com/nacos-group/nacos-sdk-go/common/constant"
"github.com/nacos-group/nacos-sdk-go/vo"
"gopkg.in/yaml.v3"
"github.com/go-kratos/kratos/v2/config"
kconfig "github.com/go-kratos/kratos/v2/config"
)
func TestConfig_Load(t *testing.T) {
sc := []constant.ServerConfig{
*constant.NewServerConfig("127.0.0.1", 8848),
}
cc := constant.ClientConfig{
TimeoutMs: 5000,
NotLoadCacheAtStart: true,
LogDir: "/tmp/nacos/log",
CacheDir: "/tmp/nacos/cache",
RotateTime: "1h",
MaxAge: 3,
LogLevel: "debug",
}
client, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &cc,
ServerConfigs: sc,
},
)
func getIntranetIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
t.Fatal(err)
return "127.0.0.1"
}
source := NewConfigSource(client, WithGroup("test"), WithDataID("test.yaml"))
type fields struct {
source config.Source
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
tests := []struct {
name string
fields fields
want []*config.KeyValue
wantErr bool
preFunc func(t *testing.T)
deferFunc func(t *testing.T)
}{
{
name: "normal",
fields: fields{
source: source,
},
wantErr: false,
preFunc: func(t *testing.T) {
_, err = client.PublishConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test", Content: "test: test"})
if err != nil {
t.Error(err)
}
time.Sleep(time.Second * 1)
},
deferFunc: func(t *testing.T) {
_, dErr := client.DeleteConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test"})
if dErr != nil {
t.Error(dErr)
}
},
want: []*config.KeyValue{{
Key: "test.yaml",
Value: []byte("test: test"),
Format: "yaml",
}},
},
{
name: "error",
fields: fields{
source: source,
},
wantErr: false,
preFunc: func(t *testing.T) {
_, err = client.PublishConfig(vo.ConfigParam{DataId: "111.yaml", Group: "notExist", Content: "test: test"})
if err != nil {
t.Error(err)
}
time.Sleep(time.Second * 1)
},
deferFunc: func(t *testing.T) {
_, dErr := client.DeleteConfig(vo.ConfigParam{DataId: "111.yaml", Group: "notExist"})
if dErr != nil {
t.Error(dErr)
}
},
want: []*config.KeyValue{{
Key: "test.yaml",
Value: []byte{},
Format: "yaml",
}},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if test.preFunc != nil {
test.preFunc(t)
}
if test.deferFunc != nil {
defer test.deferFunc(t)
}
s := test.fields.source
configs, lErr := s.Load()
if (lErr != nil) != test.wantErr {
t.Errorf("Load error = %v, wantErr %v", lErr, test.wantErr)
t.Errorf("Load configs = %v", configs)
return
}
if !reflect.DeepEqual(configs, test.want) {
t.Errorf("Load configs = %v, want %v", configs, test.want)
}
})
}
return "127.0.0.1"
}
func TestConfig_Watch(t *testing.T) {
func TestGetConfig(t *testing.T) {
ip := getIntranetIP()
// ctx := context.Background()
sc := []constant.ServerConfig{
*constant.NewServerConfig("127.0.0.1", 8848),
*constant.NewServerConfig(ip, 8848),
}
cc := constant.ClientConfig{
@ -137,6 +48,7 @@ func TestConfig_Watch(t *testing.T) {
LogLevel: "debug",
}
// a more graceful way to create naming client
client, err := clients.NewConfigClient(
vo.NacosClientParam{
ClientConfig: &cc,
@ -147,66 +59,51 @@ func TestConfig_Watch(t *testing.T) {
t.Fatal(err)
}
source := NewConfigSource(client, WithGroup("test"), WithDataID("test.yaml"))
type fields struct {
source config.Source
}
tests := []struct {
name string
fields fields
want []*config.KeyValue
wantErr bool
processFunc func(t *testing.T, w config.Watcher)
deferFunc func(t *testing.T, w config.Watcher)
}{
{
name: "normal",
fields: fields{
source: source,
},
wantErr: false,
processFunc: func(t *testing.T, w config.Watcher) {
_, pErr := client.PublishConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test", Content: "test: test"})
if pErr != nil {
t.Error(pErr)
}
},
deferFunc: func(t *testing.T, w config.Watcher) {
_, dErr := client.DeleteConfig(vo.ConfigParam{DataId: "test.yaml", Group: "test"})
if dErr != nil {
t.Error(dErr)
}
},
want: []*config.KeyValue{{
Key: "test.yaml",
Value: []byte("test: test"),
Format: "yaml",
}},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := test.fields.source
watch, wErr := s.Watch()
if wErr != nil {
t.Error(wErr)
return
}
if test.processFunc != nil {
test.processFunc(t, watch)
}
if test.deferFunc != nil {
defer test.deferFunc(t, watch)
dataID := "test.yaml"
group := "test"
_, err = client.PublishConfig(vo.ConfigParam{DataId: dataID, Group: group, Content: `
logger:
level: info
`})
if err != nil {
t.Fatal(err)
}
want, nErr := watch.Next()
if (nErr != nil) != test.wantErr {
t.Errorf("Watch error = %v, wantErr %v", nErr, test.wantErr)
return
time.Sleep(1 * time.Second)
c := kconfig.New(
kconfig.WithSource(
NewConfigSource(client, WithGroup(group), WithDataID(dataID)),
),
kconfig.WithDecoder(func(kv *kconfig.KeyValue, v map[string]interface{}) error {
return yaml.Unmarshal(kv.Value, v)
}),
)
if err = c.Load(); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(want, test.want) {
t.Errorf("Watch watcher = %v, want %v", watch, test.want)
name, err := c.Value("logger.level").String()
if err != nil {
t.Fatal(err)
}
fmt.Println("get value", name)
done := make(chan struct{})
err = c.Watch("logger.level", func(key string, value kconfig.Value) {
fmt.Println(key, " value change", value)
done <- struct{}{}
})
if err != nil {
t.Fatal(err)
}
_, err = client.PublishConfig(vo.ConfigParam{DataId: dataID, Group: group, Content: `
logger:
level: debug
`})
if err != nil {
t.Fatal(err)
}
<-done
}

@ -3,8 +3,9 @@ module github.com/go-kratos/kratos/contrib/config/nacos/v2
go 1.16
require (
github.com/go-kratos/kratos/v2 v2.6.3
github.com/go-kratos/kratos/v2 v2.3.0
github.com/nacos-group/nacos-sdk-go v1.0.9
gopkg.in/yaml.v3 v3.0.1
)
replace github.com/go-kratos/kratos/v2 => ../../../

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

Loading…
Cancel
Save