Compare commits

..

341 Commits

Author SHA1 Message Date
LinXiaoWei bffc1a0989
feat(transport): add endpoint option for server (#2904) 1 year ago
包子 32b1d13f90
deps: upgrade kratos version to v2.6.3 (#2903) 1 year ago
dependabot[bot] db2a565d1c
build(deps): bump github.com/prometheus/common (#2840) 1 year ago
jessetang fcd3b18e83
fix(encoding/form): failed to decode the map type (#2468) 1 year ago
freezeChen 3445f3ea8e
fix: logging middle don't get trace_id value when logger is filterLogger (#2869) 1 year ago
Tony Chen 32c0d2dd97
feat: add omitempty prefix to rule path (#2870) 1 year ago
xu0o0 e86ad248c3
fix(cli): fix import path with `--nomod` (#2775) 1 year ago
liaochuntao 69d73225a9
update polaris-standalone docker image (#2854) 1 year ago
Fengbin Shi 49ffd95a0c
fix: http parse pb message body panic (#2847) 1 year ago
haiyux 96480c11ee
fix: fix go import path when add project --nomod (#2837) 2 years ago
dependabot[bot] 1d50f50262
build(deps): bump actions/setup-go from 4.0.0 to 4.0.1 (#2829) 2 years ago
dependabot[bot] 6d741828c2
build(deps): bump google.golang.org/grpc in /contrib/opensergo (#2823) 2 years ago
Xudong Cai 4a56b5669d
chore: remove the dependency of grpc_testing module (#2824) 2 years ago
包子 56777ee655
deps: upgrade kratos version to v2.6.2 (#2817) 2 years ago
dependabot[bot] b1cd1d3cf8
build(deps): bump google.golang.org/grpc in /contrib/config/etcd (#2767) 2 years ago
dependabot[bot] 37a521d59f
build(deps): bump google.golang.org/grpc in /contrib/opensergo (#2768) 2 years ago
dependabot[bot] 6cf407b9bd
build(deps): bump github.com/prometheus/client_golang (#2787) 2 years ago
dependabot[bot] a904794546
build(deps): bump github.com/aliyun/aliyun-log-go-sdk (#2807) 2 years ago
chen quan a837603c6d
chore: embedding template (#2810) 2 years ago
dependabot[bot] 0b1fdbe51c
build(deps): bump go.etcd.io/etcd/client/v3 in /contrib/config/etcd (#2788) 2 years ago
dependabot[bot] 81988e6a85
build(deps): bump go.etcd.io/etcd/client/v3 in /contrib/registry/etcd (#2789) 2 years ago
Haibo 1f10166028
fix(tracing): use global provider by default, but allow custom providers (#2803) 2 years ago
dependabot[bot] c6a4604839
build(deps): bump github.com/hashicorp/consul/api (#2731) 2 years ago
Bin aed172b8dd
feat: jwt KeyFunc external return (#2809) 2 years ago
dependabot[bot] 520b321fe9
build(deps): bump golang.org/x/net in /contrib/opensergo (#2779) 2 years ago
ibrahim albarghouthi 3958f9d5c0
refactor: metadata supports key corresponding to multiple values (#2772) 2 years ago
dependabot[bot] e9870cb48f
build(deps): bump github.com/hashicorp/consul/api (#2733) 2 years ago
dependabot[bot] f8c19c37af
build(deps): bump google.golang.org/protobuf in /contrib/log/aliyun (#2751) 2 years ago
woniu317 d0847cd462
feat: log helper support sprint option (#2616) 2 years ago
jessetang f03f5f8988
fix(middleware/tracing): golang filename modify (#2727) 2 years ago
jessetang d470886977
refactor(transport): format import and init slice cap (#2741) 2 years ago
Bin 446774f9e5
fix:proto nesting causes template generation problems (#2718) 2 years ago
yonwoo9 e273c5188a
chore: update comment (#2747) 2 years ago
jessetang 393bf4dbcb
fix(contrib/registry/servicecomb): wrong stop ticker (#2749) 2 years ago
虫子樱桃 6a4d17d79a
add http.ResponseController type alias (#2713) 2 years ago
Bin 99ccd00434
fix: package path to filepath (#2672) 2 years ago
dependabot[bot] f47a238478
build(deps): bump k8s.io/client-go in /contrib/config/kubernetes (#2756) 2 years ago
dependabot[bot] 3d1af9af38
build(deps): bump k8s.io/apimachinery in /contrib/config/kubernetes (#2757) 2 years ago
jessetang 9a973d29c2
fix: rectification of non-standard lint codes (#2746) 2 years ago
jessetang 0c2d2632ac
fix(transport/grpc/resolver/discovery/resolver.go): remove attributes.Attributes.New() (#2742) 2 years ago
包子 78a2089f2b
feat: issue translate (#2744) 2 years ago
Xin d05729399e
fix: change the working directory of the command (#2560) 2 years ago
包子 8af9ca33bd
feat: support grpc balancer health check (#2736) 2 years ago
dependabot[bot] bd26120ec6
build(deps): bump actions/setup-go from 3.5.0 to 4.0.0 (#2737) 2 years ago
jessetang 6369db2e8e
fix(cmd/kratos/internal/base/repo.go): missing error (#2734) 2 years ago
jessetang 492248d032
fix(cmd): import format (#2735) 2 years ago
虫子樱桃 ae4dd7f4a8
fix: refactor project creation params process (#2714) 2 years ago
包子 33cb4576e9
Update Makefile (#2725) 2 years ago
包子 768ffd71d4
fix(grpc/balancer): fix the problem that the watch log cannot be closed (#2726) 2 years ago
jessetang 9ee4fcb48a
fix: remove repeat `net/http` package (#2708) 2 years ago
jessetang ae2dcb04c0
fix: markdown form format (#2721) 2 years ago
Xin 6602dc325e
fix: correct message of CmdServer (#2724) 2 years ago
包子 7eca8f8034
deps: upgrade kratos version to v2.6.1 (#2716) 2 years ago
yonwoo9 665a72e67f
fix: fix contrib registry eureka watcher as *watcher (#2693) 2 years ago
包子 a672980a15
fix: upgrade go.sum (#2715) 2 years ago
dependabot[bot] 51fac4ff90
build(deps): bump k8s.io/client-go in /contrib/config/kubernetes (#2695) 2 years ago
dependabot[bot] 9c7182058e
build(deps): bump github.com/hashicorp/consul/api (#2688) 2 years ago
dependabot[bot] 91be9c1071
build(deps): bump k8s.io/apimachinery in /contrib/config/kubernetes (#2694) 2 years ago
dependabot[bot] a11002d7ee
build(deps): bump github.com/hashicorp/consul/api (#2685) 2 years ago
dependabot[bot] 15de2007c7
build(deps): bump golang.org/x/sync in /contrib/registry/zookeeper (#2687) 2 years ago
dependabot[bot] 1579155a2c
build(deps): bump golang.org/x/net in /contrib/opensergo (#2699) 2 years ago
dependabot[bot] 51a34df0a6
build(deps): bump golang.org/x/net in /contrib/config/polaris (#2706) 2 years ago
dependabot[bot] 61176708e6
build(deps): bump golang.org/x/net (#2703) 2 years ago
dependabot[bot] b79c142580
build(deps): bump golang.org/x/net in /contrib/polaris (#2705) 2 years ago
dependabot[bot] f458e2535f
build(deps): bump github.com/prometheus/common (#2698) 2 years ago
dependabot[bot] 8203a90047
build(deps): bump golang.org/x/net in /contrib/registry/servicecomb (#2704) 2 years ago
dependabot[bot] c65f823c38
build(deps): bump golang.org/x/text in /cmd/protoc-gen-go-errors (#2675) 2 years ago
dependabot[bot] a6c9fdd9d3
build(deps): bump golang.org/x/text in /contrib/opensergo (#2678) 2 years ago
dependabot[bot] 7e89bbd799
build(deps): bump golang.org/x/text in /contrib/polaris (#2679) 2 years ago
包子 834b781ee2
feat: support load balance for streaming connection creation (#2669) 2 years ago
hoslo 19f008b483
feat: Add latency to the recover handler context (#2653) 2 years ago
180909 e4595db3d8
fix: cmd `proto add` nil pointer (#2670) 2 years ago
dependabot[bot] e33d644a78
build(deps): bump golang.org/x/text from 0.3.7 to 0.3.8 in /cmd/kratos (#2676) 2 years ago
180909 d8d231e725
fix(selector): version NodeFilter append func (#2671) 2 years ago
weetime bab67facad
fix(contrib): fix eureka server load too high (#2546) 2 years ago
thinkgo d0b1d84850
feat: add comment to http server interface method (#2657) 2 years ago
dependabot[bot] 54f19c1dcb
build(deps): bump github.com/prometheus/common (#2668) 2 years ago
dependabot[bot] 8d5010495a
build(deps): bump google.golang.org/grpc in /contrib/config/etcd (#2662) 2 years ago
jessetang 6330a5688e
chore: replace deprecated method (#2667) 2 years ago
包子 27eadd83b4
fix: polaris get service name (#2615) 2 years ago
YuanXin Hu 0a076443cb
feat: support polaris ratelimit ability (#2586) 2 years ago
aveyuan 7def38acde
feat(log): update zap interface (#2620) 2 years ago
LiuDui 77abb6356f
chore: update pull request template (#2649) 2 years ago
包子 50da181d69
fix: do not re register after service logout (#2647) 2 years ago
包子 a006328db6
fix: polaris temporarily delete the watch test (#2652) 2 years ago
leyou240 613282b096
chore(ci): update go.yaml support golang 1.20 (#2651) 2 years ago
LiuDui 08f37391e1
chore: clean up deprecated code (#2648) 2 years ago
cui fliter 239121155d
chore: fix comment (#2645) 2 years ago
jerjjj b242403bc1
update gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b to 3.0.0 (#2630) 2 years ago
Kevin Wan 63b23af418
chore: update go-zero link in readme (#2628) 2 years ago
liyaopinner 35800916dc
feat: add grpc client stream interceptor opts (#2610) 2 years ago
longxboy bebea0c103
feat: add subset alg for instances (#2613) 2 years ago
虫子樱桃 61744753eb
fix: project creation `--nomod` (#2611) 2 years ago
桂后昌 b2689af39c
feat(transport/http): request body read multiple times (#2542) 2 years ago
180909 e22775cfcc
chore(transport): fix the test name (#2543) 2 years ago
cui fliter 9cc1047c75
chore: fix a few function names on comments (#2430) 2 years ago
包子 7e896ae4c0
feat: support polaris service governance (#2605) 2 years ago
包子 33d51a84c3
feat(registry): consul support re-registry when service does not exist (#2606) 2 years ago
longxboy eafbe908a8
feat:add buf apis (#2604) 2 years ago
dependabot[bot] f49ac647e8
build(deps): bump github.com/aliyun/aliyun-log-go-sdk (#2598) 2 years ago
baeNewJeans 65c51594f9
fix(contrib/registry/etcd): rewatch when error occur (#2603) 2 years ago
包子 480b16b817
feat: grpc use the admin api (#2596) 2 years ago
dependabot[bot] b26023888d
build(deps): bump github.com/hashicorp/consul/api (#2561) 2 years ago
刘思圆 a3f24ee704
fix(contrib/log/tencent): tencentcloud-cls-sdk-go 为异步上传,需要显式调用"Start"方法开启才会生效 (#2514) 2 years ago
Remember 271b6c2924
fix(consul):return err if ctx is done (#2550) 2 years ago
Luckystar c442a320a0
feat: support for windows make install (#2554) 2 years ago
soukengo b0db594829
fix(registry): add zookeeper exists watcher when the node does not exist (#2555) 2 years ago
dependabot[bot] 33fff02a62
build(deps): bump github.com/aliyun/aliyun-log-go-sdk (#2577) 2 years ago
Juan C. Yamacho H d12498ed38
feat(middleware): redacter interface for logging (#2564) 2 years ago
虫子樱桃 852a77faa6
Feat project create with dir name add test (#2576) 2 years ago
dependabot[bot] 6f78ccacd9
build(deps): bump k8s.io/apimachinery in /contrib/config/kubernetes (#2581) 2 years ago
dependabot[bot] 2c3fab50a3
build(deps): bump actions/setup-go from 3.3.1 to 3.5.0 (#2585) 2 years ago
dependabot[bot] 2a9808df14
build(deps): bump github.com/polarismesh/polaris-go (#2589) 2 years ago
dependabot[bot] 89d8eeba62
build(deps): bump github.com/apolloconfig/agollo/v4 (#2591) 2 years ago
dependabot[bot] a017ab0957
build(deps): bump github.com/hashicorp/consul/api (#2563) 2 years ago
baozhecheng a45f3afdff Revert "feat: support polaris service governance" 2 years ago
baozhecheng b67d514bfa feat: support polaris service governance 2 years ago
Xingwang Liu facafba64a
fix(registry): ServiceInstance does not implement an Equal method for grpc Attributes. (#2575) 2 years ago
baozhecheng e36612e9ca Revert "fix issue:#2358 Support for creating a project with specifying the name for its place dir (#2573)" 2 years ago
虫子樱桃 3d322fe6c1
fix issue:#2358 Support for creating a project with specifying the name for its place dir (#2573) 2 years ago
woniu317 2cf82fa4a7
feat: consul registry support get services from multiple datacenters (#2536) 2 years ago
jessetang 3393990cd8
test(encoding/form/encode): add test and fix code style (#2539) 2 years ago
dependabot[bot] 3c3829795c
build(deps): bump github.com/aliyun/aliyun-log-go-sdk (#2548) 2 years ago
Ccheers 8732b76386
fix(circuitbreaker): add WithCircuitBreaker to fix WithGroup is useless (#2535) 2 years ago
Aurélien Perrier d779faf091
feat(transport/grpc): support custom healthcheck (#2541) 2 years ago
qshuai a82c82d49f
fix(config): fix codec NPE as parsing format wrongly, fix #2517 (#2518) 2 years ago
180909 c530d63e75
chore(cmd): use t.Cleanup (#2537) 2 years ago
包子 a7bae93ee0
deps: upgrade kratos version to v2.5.3 (#2500) 2 years ago
dependabot[bot] b339ae8956
build(deps): bump github.com/apolloconfig/agollo/v4 (#2421) 2 years ago
dependabot[bot] 3f698aa2c0
build(deps): bump go.etcd.io/etcd/client/v3 in /contrib/registry/etcd (#2384) 2 years ago
dependabot[bot] c5888ff464
build(deps): bump google.golang.org/grpc in /contrib/opensergo (#2452) 2 years ago
dependabot[bot] 878eca54ab
build(deps): bump google.golang.org/grpc in /contrib/config/etcd (#2453) 2 years ago
yeqown 275f815e40
typo: correct var name (#2496) 2 years ago
Cluas 383f28faeb
fix(selector): set global do not work (#2489) 2 years ago
dependabot[bot] e9ef3eea2d
build(deps): bump github.com/hashicorp/consul/api (#2472) 2 years ago
dependabot[bot] 58d9e3be56
build(deps): bump github.com/prometheus/client_golang (#2494) 2 years ago
dependabot[bot] 818452dd53
build(deps): bump github.com/polarismesh/polaris-go (#2493) 2 years ago
dependabot[bot] 699f0304ed
build(deps): bump github.com/aliyun/aliyun-log-go-sdk (#2492) 2 years ago
dependabot[bot] c888d50cc5
build(deps): bump google.golang.org/grpc in /contrib/registry/etcd (#2454) 2 years ago
jessetang a1a2b9dbd9
test(encoding/form): add unit test and code style modift (#2467) 2 years ago
JellyTony 590c469081
feat: add app Before and Afters option (#2403) 2 years ago
Miguel Martínez Triviño 0f38511ea8
fix(filter): filter out keyvals if keyFunc is provided (#2484) 2 years ago
jessetang 511f1917ce
fix(contrib/log): test case error (#2487) 2 years ago
jessetang 2a65502be2
style(config): code style tweaks and abstracts some methods (#2465) 2 years ago
dependabot[bot] 03d9494b19
build(deps): bump github.com/hashicorp/consul/api (#2473) 2 years ago
dependabot[bot] c4c1ebab47
build(deps): bump github.com/aliyun/aliyun-log-go-sdk (#2483) 2 years ago
Tony Chen f0878b0a78
fix ewma panic (#2480) 2 years ago
jessetang 3c65f16737
style(contrib/config): code style modify (#2475) 2 years ago
包子 dcae38d656
deps: upgrade kratos version to v2.5.2 (#2464) 2 years ago
Jesse a911f8f9ee
fix(log): toString float32 precision loss and convert uint use `FormatUint` (#2461) 2 years ago
dependabot[bot] 4bd1fde7ef
build(deps): bump actions/setup-go from 3.3.0 to 3.3.1 (#2462) 2 years ago
Jesse 54655e4b24
cleanup: use HTTP package methods replace GET POST DELETE... (#2412) 2 years ago
Tony Chen 185aaa7184
fix net error (#2460) 2 years ago
Guoqiang Ding 40300677ee
fix(metadata): sort services by lexicographical order on ListServices (#2397) 2 years ago
Jesse 6bb3ad6f27
fix: in for defer close (#2411) 2 years ago
Jesse 54c6fb3631
test(middleware/auth/jwt): add test TestNewContextAndFromContext (#2447) 2 years ago
Jesse b5482d1794
test(middleware/metadata): supplement test and modify code style (#2448) 2 years ago
Guoqiang Ding e3feea6eeb
docs(config/etcd): fix the missing step of Load (#2450) 2 years ago
ruohone d82d607c21
[Feature] add path prefix for http server (#2439) 2 years ago
jesse.tang 7a99e8bbdc
cleanup: regex replcae \. => . (#2435) 2 years ago
jesse.tang 77d260241c
test(middleware/logging): TestExtractError (#2443) 2 years ago
jesse.tang 2de6ba028c
cleanup: update scpSyntaxRe regexp (#2444) 2 years ago
jesse.tang b5cd1c693d
fix: contrib/registry/eureka/client.go serveice -> serivce (#2440) 2 years ago
jesse.tang c5e86ca36d
style: kratos interface check (#2441) 2 years ago
包子 8743f3e50c
deps: upgrade kratos version v2.5.1 (#2431) 2 years ago
jesse.tang 69fcd50c0a
feat: golangci add goimports local-prefixes (#2413) 2 years ago
jesse.tang 9f65c1a03d
cleanup: remove redundancy type conversion and golangci add unconvert (#2409) 2 years ago
Tony Chen a680321309
add custom binding (#2428) 2 years ago
Tony Chen 468630cc4b
expose request from transport (#2427) 2 years ago
180909 9301bfa407
chore(log): let log toString same (#2414) 2 years ago
leyou240 ff59a89b06
chore: Update go.yml (#2426) 2 years ago
jesse.tang 0ecc2b422f
style: modify declaring empty slices (#2378) 2 years ago
Gaffey faa3adc300
style(config/value): optimize atomicValue format string (#2401) 2 years ago
180909 b10e067fe9
test(log): add toString unit test (#2373) 2 years ago
jesse.tang 0e698b248a
cleanup: remove fmt and errors new err (#2377) 2 years ago
180909 2936b5ba80
cleanup: remove the repeated import (#2393) 2 years ago
jesse.tang 4d95050747
fix: fmt import (#2379) 2 years ago
swliao425 8d067a32db
fix(aliyun log): 'format bool' is missing in 'toString' function (#2406) 2 years ago
180909 2170a12aa4
fix: modify interface check way on selector (#2399) 2 years ago
180909 add67beeb3
fix(http/binding): fix http encode url (#2400) 2 years ago
包子 7866ff75fd
fix(registry/consul): use health check option to control custom checks (#2391) 2 years ago
Tony Chen 2d325fd386
fix(http/binding): fix http encode url (#2392) 2 years ago
jesse.tang 080165f2ee
cleanup: modify deprecated field (#2380) 2 years ago
jesse.tang 9737a3c5e4
fix: typo and grammatical errors (#2368) 2 years ago
Jiepeng Cao 7f2666be9f
typo for log (#2374) 2 years ago
jesse.tang 7a320233cd
fix: .gitignore del Repeated .*so (#2367) 2 years ago
Loner1024 d3f80c1061
test: supplement the unit testing of transport/grpc (#2371) 2 years ago
jesse.tang b0b49be383
feat: remote assert libraries (#2372) 2 years ago
jesse.tang 0ed01efdfd
revise err logic or null pointer exception (#2376) 2 years ago
jesse.tang f2a33929d0
fix: modify interface check way (#2375) 2 years ago
包子 ae505063fe
fix(apollo): config return nil when watch err 2 years ago
Guoqiang Ding 667d63839c
fix(makefile): add "make proto" for proto generating (#2130) 2 years ago
hshe e176ddfcdd
feat(internal/host): prefer ipv4 than ipv6 (#2342) 2 years ago
180909 0f0c75e20b
feat(cmd): user prePage to check changelog dev (#2340) 2 years ago
180909 18c5734930
test(cmd): add ModulePath test (#2337) 2 years ago
180909 8d76eebf8b
test(cmd/change): add ParseGithubURL test (#2339) 2 years ago
GongGuoWei cb69bf3714
test(config): add etcd config method test (#2349) 2 years ago
黄仲辉 617ee1aa39
fix(lint): Using deprecated package `io/ioutil` cause lint interrupted, change to `os` or `io` package. (#2353) 2 years ago
180909 39536d3279
fix(cmd): fix CmdAdd long message example (#2343) 2 years ago
dependabot[bot] e820b392e9
build(deps): bump github.com/hashicorp/consul/api (#2279) 2 years ago
dependabot[bot] aaabd07474
build(deps): bump actions/setup-go from 3.2.1 to 3.3.0 (#2313) 2 years ago
dependabot[bot] 7a336ca353
build(deps): bump google.golang.org/protobuf in /contrib/opensergo (#2248) 2 years ago
dependabot[bot] 503efd3825
build(deps): bump go.uber.org/zap in /contrib/log/zap (#2326) 2 years ago
icylight 60b00c8ade
fix(config/env): prefixs typo (#2321) 2 years ago
Wang-TaoTao ad7597c0b1
fix: init global selector to wrr (#2323) 2 years ago
icylight cbfb6db9cf
fix(internal/host): Extract can't return the minium index ip (#2298) 2 years ago
虫子樱桃 20c2425c18
fix `kratos new -r` not support scp styled git url.issue https://gith… (#2295) 2 years ago
包子 890bc21ac1
docs: update readme.md, remove qq group (#2341) 2 years ago
川桑 361391732b
test(selector): improve coverage (#2320) 2 years ago
包子 b354f185c0
feat(registry): consul support user custom checks (#2317) 2 years ago
jesse.tang 73a8323ee7
fix: slice/map make init cap (#2300) 2 years ago
Germiniku cb6176dbbb
feat(tracing) custom tracer name (#2310) 2 years ago
过客龙门 0ce9e8d069
specify array capacity hints (#2299) 2 years ago
dependabot[bot] b1bb0bd18f
build(deps): bump github.com/hashicorp/consul/api (#2278) 2 years ago
dependabot[bot] 869fcd57c8
build(deps): bump go.uber.org/zap in /contrib/log/zap (#2280) 2 years ago
icylight 5c3f0aa73e
style(metadata): specify map capacity hints (#2296) 2 years ago
jesse.tang 6ca225b078
add notes by not used defer (#2291) 2 years ago
YidaZhou ba72230477
doc: grammar problems in the Readme file (#2281) 2 years ago
Astone 205aa88d86
fix: encoding form decode field mask from well-know (#2285) 2 years ago
pwli bdfeb4bf78
fix typo (#2284) 2 years ago
haiyux 39796ea0dc
fix: delete endpoint service discovery compatibility (#2289) 2 years ago
包子 563ad0d34a
deps: upgreade kratos version to v2.5.0 (#2286) 2 years ago
包子 3f31b4d734
bc: apollo unable to get and watch to the properties file (#2269) 2 years ago
Tony Chen 2d206076f8
fix logger caller depth (#2283) 2 years ago
Germiniku de2f93fbec
refactor(log) aliyun use the same interface name (#2251) 2 years ago
longxboy 11cd43e3c3
refactor: unify selector filter (#2277) 2 years ago
kkf1 d11c6892b4
feat: add servicecomb registry (#2114) 2 years ago
Haibo 6d665c0ce6
fix: wrong order of Logger prefix kvs (#2273) 2 years ago
包子 84235462b7
feat(registry): consul get service support remote (#2275) 2 years ago
SeniorPlayer b9b7888d51
fix(log): DefaultCaller doesn't returns "pkg/file:line", it returns "file:line" now. For example, both biz/user.go and data/user.go are printed as user.go in logger. It's not clear. (#2274) 2 years ago
realityone f0c2a6ed90
Global logger (#2265) 2 years ago
Weizhen Wang 57dee517e5
fix(chore): set nacos-server v2.1.0 and consul v1.12.3 (#2268) 2 years ago
Kagaya e4d73db523
fix lint error (#2266) 2 years ago
dependabot[bot] 0aa9719e3a
build(deps): bump google.golang.org/protobuf in /contrib/log/aliyun (#2249) 2 years ago
shifengbin c407afc81d
fix: in case url or form bind param error should return BadRequest (#2256) 2 years ago
Germiniku fea863e783
feat(log): add tencent cls (#2244) 2 years ago
林晓炜 1f3ac4ea22
fix(registry): contrib/registry/zookeeper ephemeral nodes handling after restart (#2245) 2 years ago
dependabot[bot] aae0339692
build(deps): bump github.com/prometheus/common (#2210) 2 years ago
包子 eff368621f
deps: upgrade kratos version to v2.4.1 (#2242) 2 years ago
kiripeng a5eb913aa0
chore: go workspace contain go.work and go.work.sum (#2241) 2 years ago
180909 ffa1c2696c
update ignore for go workspace (#2240) 2 years ago
dependabot[bot] 86eba94646
build(deps): bump k8s.io/client-go in /contrib/config/kubernetes (#2217) 2 years ago
dependabot[bot] 2951b420f1
build(deps): bump k8s.io/client-go in /contrib/registry/kubernetes (#2218) 2 years ago
Tony Chen f3b0da3f04
feat(middleware): add selector matcher (#2239) 2 years ago
dependabot[bot] 377356d04d
build(deps): bump github.com/hashicorp/consul/api (#2211) 2 years ago
dependabot[bot] 247a74e631
build(deps): bump github.com/hashicorp/consul/api (#2209) 2 years ago
Tony Chen 3c54997dd8
fix default behavior for gRPC encoding (#2187) 2 years ago
dependabot[bot] e6767bc612
build(deps): bump k8s.io/api in /contrib/config/kubernetes (#2219) 2 years ago
dependabot[bot] 9d24dac487
build(deps): bump k8s.io/api in /contrib/registry/kubernetes (#2220) 2 years ago
dependabot[bot] e2c1fd7489
build(deps): bump github.com/go-zookeeper/zk (#2236) 2 years ago
hshe b7422717cf
proto build client structName not match to server's structName (#2200) 2 years ago
Haibo 59b758ceda
feat: support passing program args with run cmd (#2207) 2 years ago
林晓炜 246d8d9c28
fix: zookeeper add auto re-register (#2235) 2 years ago
Paul 92b3c8f94a
fix(contrib/config/polaris): Use injected client properly (#2238) 2 years ago
haiyux 14cfd65b62
fix: remove unuseful file when use nomod (#2234) 2 years ago
wangcong 82dfb955f5
fix(contrib): add logrus fatal level (#2222) 2 years ago
风雨雾凇 de0e4e2228
docs: update README_zh.md (#2208) 2 years ago
dependabot[bot] 1ab3d8f028
build(deps): bump github.com/prometheus/common (#2188) 2 years ago
dependabot[bot] 0ff80e42e7
build(deps): bump actions/setup-go from 3.2.0 to 3.2.1 (#2189) 2 years ago
Walrus Yu d1da8aa1ae
Feature/improve app test (#2190) 2 years ago
shengzhou ede5f889d4
test: increase endpoint,env, options and reader tests coverage. (#2192) 2 years ago
rogerogers bcef2b8e3f
test(contrib): update unit test for contrib/registry/polaris (#2196) 2 years ago
jakezhu9 85af73a84b
test(log): increase tests coverage (#2197) 2 years ago
包子 1451b9e0c0
deps: upgrade kratos version to v2.4.0 (#2186) 2 years ago
潘雄 96c8de9bc4
style(contrib/registry/*) annotation perfect (#2185) 2 years ago
Tony Chen d0a0edf67b
feat(http): add http router walk (#2181) 2 years ago
jakezhu9 afd108cdc7
test(options): increase tests coverage (#2183) 2 years ago
Ccheers b3eff576ce
test(transport): add unit test for transport coverage: 91.7% of state… (#2172) 2 years ago
rogerogers 63827466a3
test(contrib): add unit test for contrib/metrics/prometheus (#2182) 2 years ago
freezeChen 187c65bf8c
test(contrib/config/consul): add unit test for contrib/config/consul (#2179) 2 years ago
Tony Chen 6ac63e6439
fix(transport): http endpoint listening (#2180) 2 years ago
Tony Chen c90eab4577
fix transport early listening (#2177) 2 years ago
川桑 102391165e
test(errors): improve coverage (#2178) 2 years ago
Giovanny Gutiérrez 2209da5e24
feat(metadata): Ignore service if the dependencies are not found (#2171) 2 years ago
dependabot[bot] 1ea4ef7412
build(deps): bump k8s.io/client-go in /contrib/registry/kubernetes (#2120) 2 years ago
川桑 d88052b7dd
fix(errors): panic when err of Clone is nil (#2176) 2 years ago
darkweak c9fbb27b5b
tests(coverage): Increase middleware tests coverage (#2165) 2 years ago
shengzhou dec323113f
test(config/file): add format test (#2147) (#2168) 2 years ago
dependabot[bot] 8d0d7838ca
build(deps): bump github.com/hashicorp/consul/api (#2082) 2 years ago
rogerogers 2525e81e55
test(contrib): add unit test for contrib/registry/k8s (#2166) 2 years ago
dependabot[bot] 08fccfcb66
build(deps): bump github.com/hashicorp/consul/api (#2081) 2 years ago
dependabot[bot] 8e9e15e04b
build(deps): bump k8s.io/client-go in /contrib/config/kubernetes (#2122) 2 years ago
Betula-L e09d294388
feat: add logrus to contrib log (#2077) 2 years ago
dependabot[bot] 39bfeeb2f7
build(deps): bump k8s.io/api in /contrib/registry/kubernetes (#2124) 2 years ago
Y.Horie 106bc07def
test(contrib): add unit test for aliyun.go (#2164) 2 years ago
Tony Chen 0bcdb8d59c
fix(encoding/form): encode optional value (#2155) 2 years ago
shengzhou 5df19c47d8
test(encoding/form): well konw types test (#2147) (#2160) 2 years ago
Loner1024 62b58848d6
test: supplement the unit testing of metadata (#2161) 2 years ago
dependabot[bot] b6954d1aeb
build(deps): bump github.com/aliyun/aliyun-log-go-sdk (#2152) 2 years ago
rogerogers 6c36cc9544
feat: add contrib/config/polaris polaris as configcenter (#2158) 2 years ago
wangcong 20b7c9f4d7
test(nacos): add nacos unit test and remove unused function (#2145) 2 years ago
haiyux addcbba66e
feat: add protoc-gen-go-http annotations synchronization with protoc-gen-go-grpc (#2151) 2 years ago
Guoqiang Ding 035c2f5387
fix: typo on emptypb.Empty (#2150) 2 years ago
rogerogers 8c0a354797
test(contrib): add unit test for contrib/config/k8s (#2140) 2 years ago
freezeChen ed8b2352b7
test(contrib): add unit test for contrib/registry/consul (#2148) 2 years ago
liuyi0618 52031f1381
feat(contrib):add etcd watcher withKeysOnly option (#2146) 2 years ago
Y.Horie 9015ffb920
test(contrib): add unit test for fluent.go (#2141) 2 years ago
ThereWeGo 4ca25e4679
fix: show git output (#2143) 2 years ago
shifengbin 2ad7ffdd7e
fix: remove the output of the proto command 2 years ago
zoujiejun 3905182e98
test(contrib): add unit test for opensergo.go (#2137) 2 years ago
Ccheers 13c33fc4d3
feat(contrib): add unit test for zap_test.go & register_test.go (#2136) 2 years ago
haiyux 25c5996360
fix:fix url ptr not encode (#2117) 2 years ago
Guoqiang Ding 2f8703bff2
fix: allow the case of proto not found, such like gogo.proto (#2132) 2 years ago
charviki a3439c9713
fix: catch server stop error (#2125) 2 years ago
Tony Chen a0e624c0b8
fix(binding): fix bind field mask (#2112) 2 years ago
Cluas 6fa5700c3c
chore(contrib/config): uniformly canceled by CancelFunc (#2111) 2 years ago
包子 3aaac45e3d
fix: regenerate the proto file (#2108) 2 years ago
longxboy 2a989ea0fe
fix dup gen const for http (#2102) 2 years ago
YuanXin Hu e3c37b936f
use offical image (#2105) 2 years ago
Cluas 4f8d8ef8da
feat(config): add ErrorWatcherStopped and return on watcher.Next() after Watcher.Stop() (#2092) 2 years ago
haiyux 752f011ba1
fix:fix proto add underscore problem (#2101) 2 years ago
raw34 228ceaae9d
fix: kratos proto server cmd method name bugfix (#2089) (#2094) 2 years ago
包子 4b7afe52af
fix(config): apollo get the original configuration file directly (#2100) 2 years ago
longxboy 123fc1e6c8
feat: add peer for selector (#2088) 2 years ago
longxboy eb280903f0
fix: operation http constant name conflict (#2087) 2 years ago
longxboy 93adaba60a
add http operation const (#2086) 2 years ago
包子 874d4c3edc
doc: update pull request template (#2080) 2 years ago
workman-Lu d1550b366b
The clear interaction for user (#1916) 2 years ago
虫子樱桃 c6f6caa887
Add Flusher type alias . (#2078) 2 years ago
包子 95dce00852
deps: upgrade kratos version to v.2.3.1 (#2075) 2 years ago
haiyux ad5e9032a3
feat: make secure url to grpcs://127.0.0.1/ (#2072) 2 years ago
Tony Chen 1b3529fd0b
transport/http(feat): add redirector to forward request (#2074) 2 years ago
dependabot[bot] 5de1f081f6
build(deps): bump gopkg.in/yaml.v3 in /contrib/config/nacos (#2067) 3 years ago
dependabot[bot] b5e7b8d61b
build(deps): bump gopkg.in/yaml.v3 in /contrib/config/apollo (#2068) 3 years ago
Tony Chen 97c103a395
fix with logger (#2062) 3 years ago
dependabot[bot] b74e330289
build(deps): bump k8s.io/client-go in /contrib/config/kubernetes (#2056) 3 years ago
dependabot[bot] 7d425b898e
build(deps): bump k8s.io/client-go in /contrib/registry/kubernetes (#2057) 3 years ago
dependabot[bot] 8ad8578c40
build(deps): bump github.com/aliyun/aliyun-log-go-sdk (#2053) 3 years ago
dependabot[bot] a926195f66
build(deps): bump k8s.io/api in /contrib/config/kubernetes (#2052) 3 years ago
Tony Chen dccee86141
log: remove component logger to use global logger (#2061) 3 years ago
dependabot[bot] 925a0b725e
build(deps): bump k8s.io/api in /contrib/registry/kubernetes (#2058) 3 years ago
包子 6e685a474b
fix: config log 3 years ago
包子 c01fd054ec
feat: add config log (#2060) 3 years ago
Tony Chen 503ec03f37
app: fix instance nil when not registered (#2059) 3 years ago
dependabot[bot] 84b23fd301
build(deps): bump actions/setup-go from 2 to 3.2.0 (#2048) 3 years ago
dependabot[bot] 8fd0dc7680
build(deps): bump golangci/golangci-lint-action from 2 to 3 (#2047) 3 years ago
dependabot[bot] 6be806e7c0
build(deps): bump actions/checkout from 2 to 3 (#2046) 3 years ago
dependabot[bot] d1c6b732b4
build(deps): bump github/codeql-action from 1 to 2 (#2045) 3 years ago
包子 65d0ddbaae
Update dependabot.yml 3 years ago
包子 c304680444
Create dependabot.yml 3 years ago
longxboy 944d480f78
feat:add context for log (#2041) 3 years ago
  1. 2
      .github/ISSUE_TEMPLATE/feature-request.md
  2. 87
      .github/dependabot.yml
  3. 13
      .github/pull_request_template.md
  4. 12
      .github/stable.yml
  5. 6
      .github/workflows/codeql-analysis.yml
  6. 2
      .github/workflows/gitee-sync.yml
  7. 28
      .github/workflows/go.yml
  8. 16
      .github/workflows/issue-translator.yml
  9. 6
      .github/workflows/lint.yml
  10. 5
      .gitignore
  11. 3
      .golangci.yml
  12. 8
      CONTRIBUTING.md
  13. 31
      Makefile
  14. 27
      README.md
  15. 24
      README_zh.md
  16. 8
      SECURITY.md
  17. 168
      api/metadata/metadata.pb.go
  18. 6
      api/metadata/metadata_grpc.pb.go
  19. 2
      api/metadata/metadata_http.pb.go
  20. 29
      api/metadata/server.go
  21. 91
      app.go
  22. 111
      app_test.go
  23. 10
      cmd/kratos/go.mod
  24. 38
      cmd/kratos/go.sum
  25. 34
      cmd/kratos/internal/base/mod_test.go
  26. 23
      cmd/kratos/internal/base/path.go
  27. 45
      cmd/kratos/internal/base/repo.go
  28. 50
      cmd/kratos/internal/base/repo_test.go
  29. 58
      cmd/kratos/internal/base/vcs_url.go
  30. 55
      cmd/kratos/internal/base/vcs_url_test.go
  31. 2
      cmd/kratos/internal/change/change.go
  32. 9
      cmd/kratos/internal/change/get.go
  33. 25
      cmd/kratos/internal/change/get_test.go
  34. 13
      cmd/kratos/internal/project/add.go
  35. 16
      cmd/kratos/internal/project/new.go
  36. 73
      cmd/kratos/internal/project/project.go
  37. 29
      cmd/kratos/internal/project/project_linux_test.go
  38. 144
      cmd/kratos/internal/project/project_test.go
  39. 30
      cmd/kratos/internal/project/project_windows_test.go
  40. 19
      cmd/kratos/internal/proto/add/add.go
  41. 38
      cmd/kratos/internal/proto/add/add_test.go
  42. 6
      cmd/kratos/internal/proto/add/proto.go
  43. 6
      cmd/kratos/internal/proto/client/client.go
  44. 8
      cmd/kratos/internal/proto/proto.go
  45. 37
      cmd/kratos/internal/proto/server/server.go
  46. 102
      cmd/kratos/internal/proto/server/server_test.go
  47. 4
      cmd/kratos/internal/proto/server/template.go
  48. 31
      cmd/kratos/internal/run/run.go
  49. 6
      cmd/kratos/internal/upgrade/upgrade.go
  50. 4
      cmd/kratos/main.go
  51. 2
      cmd/kratos/version.go
  52. 5
      cmd/protoc-gen-go-errors/errors.go
  53. 17
      cmd/protoc-gen-go-errors/errorsTemplate.tpl
  54. 2
      cmd/protoc-gen-go-errors/go.mod
  55. 24
      cmd/protoc-gen-go-errors/go.sum
  56. 22
      cmd/protoc-gen-go-errors/template.go
  57. 2
      cmd/protoc-gen-go-errors/version.go
  58. 72
      cmd/protoc-gen-go-http/http.go
  59. 93
      cmd/protoc-gen-go-http/httpTemplate.tpl
  60. 7
      cmd/protoc-gen-go-http/main.go
  61. 91
      cmd/protoc-gen-go-http/template.go
  62. 2
      cmd/protoc-gen-go-http/version.go
  63. 34
      config/config.go
  64. 46
      config/config_test.go
  65. 16
      config/env/env.go
  66. 26
      config/env/env_test.go
  67. 4
      config/env/watcher.go
  68. 16
      config/file/file_test.go
  69. 43
      config/file/format_test.go
  70. 5
      config/file/watcher.go
  71. 8
      config/options.go
  72. 43
      config/options_test.go
  73. 20
      config/reader.go
  74. 167
      config/reader_test.go
  75. 70
      config/value.go
  76. 75
      config/value_test.go
  77. 4
      contrib/config/apollo/README.md
  78. 206
      contrib/config/apollo/apollo.go
  79. 161
      contrib/config/apollo/apollo_test.go
  80. 9
      contrib/config/apollo/go.mod
  81. 995
      contrib/config/apollo/go.sum
  82. 34
      contrib/config/apollo/parser.go
  83. 58
      contrib/config/apollo/watcher.go
  84. 11
      contrib/config/consul/README.md
  85. 7
      contrib/config/consul/config.go
  86. 158
      contrib/config/consul/config_test.go
  87. 9
      contrib/config/consul/go.mod
  88. 1583
      contrib/config/consul/go.sum
  89. 37
      contrib/config/consul/watcher.go
  90. 17
      contrib/config/etcd/README.md
  91. 3
      contrib/config/etcd/config.go
  92. 61
      contrib/config/etcd/config_test.go
  93. 6
      contrib/config/etcd/go.mod
  94. 1460
      contrib/config/etcd/go.sum
  95. 37
      contrib/config/etcd/watcher.go
  96. 3
      contrib/config/kubernetes/config.go
  97. 170
      contrib/config/kubernetes/config_test.go
  98. 8
      contrib/config/kubernetes/go.mod
  99. 1229
      contrib/config/kubernetes/go.sum
  100. 3
      contrib/config/kubernetes/watcher.go
  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 ### Requirements description of the feature
<!-- <!--
example: example:
The event interface should be added to Kratos. The interface should contain subscribers and publishers, and the message body should contain key value heade The event interface should be added to Kratos. The interface should contain subscribers and publishers, and the message body should contain key value head
--> -->
### References ### References
<!-- <!--

@ -0,0 +1,87 @@
---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "/contrib/config/apollo"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/config/consul"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/config/etcd"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/config/kubernetes"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/config/nacos"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/encoding/msgpack"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/log/aliyun"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/log/zap"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/log/fluent"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/metrics/datadog"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/metrics/prometheus"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/opensergo"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/registry/apollo"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/registry/consul"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/registry/etcd"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/registry/kubernetes"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/registry/nacos"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/registry/zookeeper"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/registry/eureka"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directory: "/contrib/registry/polaris"
schedule:
interval: "weekly"

@ -3,10 +3,21 @@
1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/ 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. 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 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): #### Description (what this PR does / why we need it):
<!-- <!--
* The description should include the motivation for this PR or contrast this with previous behavior * The description should include the motivation for this PR or contrast this with previous behavior

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

@ -11,12 +11,12 @@ jobs:
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v2
with: with:
languages: go languages: go
- name: CodeQL Analysis - name: CodeQL Analysis
uses: github/codeql-action/analyze@v1 uses: github/codeql-action/analyze@v2

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@v1 uses: actions/checkout@v3
- name: Mirror Github to Gitee - name: Mirror Github to Gitee
uses: Yikun/hub-mirror-action@v1.2 uses: Yikun/hub-mirror-action@v1.2
with: with:

@ -13,7 +13,7 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
go: [1.16, 1.17, 1.18] go: [1.19,1.20.x]
name: build & test name: build & test
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
@ -25,28 +25,44 @@ jobs:
ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379 ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379
ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379
consul: consul:
image: consul:latest image: consul:1.12.3
ports: ports:
- 8500:8500 - 8500:8500
nacos: nacos:
image: nacos/nacos-server:latest image: nacos/nacos-server:v2.1.0
env: env:
MODE: standalone MODE: standalone
ports: ports:
- "8848:8848" - "8848:8848"
- "9848:9848" - "9848:9848"
polaris: polaris:
image: houseme/polaris-server-with-config:latest image: polarismesh/polaris-standalone:latest
ports: ports:
- 8090:8090 - 8090:8090
- 8091:8091 - 8091:8091
- 8093:8093
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v2 uses: actions/setup-go@v4.0.1
with: with:
go-version: ${{ matrix.go }} 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 - name: Build
run: go build ./... run: go build ./...

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

@ -15,7 +15,7 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }} matrix: ${{ steps.set-matrix.outputs.matrix }}
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@v2 uses: actions/checkout@v3
- id: set-matrix - id: set-matrix
run: ./hack/resolve-modules.sh run: ./hack/resolve-modules.sh
@ -27,9 +27,9 @@ jobs:
strategy: strategy:
matrix: ${{ fromJson(needs.resolve-modules.outputs.matrix) }} matrix: ${{ fromJson(needs.resolve-modules.outputs.matrix) }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Lint - name: Lint
uses: golangci/golangci-lint-action@v2 uses: golangci/golangci-lint-action@v3
with: with:
version: latest version: latest
working-directory: ${{ matrix.workdir }} working-directory: ${{ matrix.workdir }}

5
.gitignore vendored

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

@ -31,6 +31,7 @@ linters:
- varcheck - varcheck
- whitespace - whitespace
- wastedassign - wastedassign
- unconvert
# don't enable: # don't enable:
# - asciicheck # - asciicheck
@ -66,3 +67,5 @@ linters-settings:
gocyclo: gocyclo:
# recommend 10-20 # recommend 10-20
min-complexity: 50 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. 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.
## Reportings Bug or Fixing Bugs ## 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). 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. 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. After the merge is completed, Close all issues.
## How to submit code ## 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 - Then create a new feature branch based on the main branch and name it features such as feature-log
- Write code - Write code
- Submit code to the far end branch - Submit code to the far end branch

@ -1,9 +1,27 @@
user := $(shell whoami) user := $(shell whoami)
rev := $(shell git rev-parse --short HEAD) rev := $(shell git rev-parse --short HEAD)
os := $(shell expr substr $(shell uname -s) 1 5)
# GOBIN > GOPATH > INSTALLDIR # GOBIN > GOPATH > INSTALLDIR
# Mac OS X
ifeq ($(shell uname),Darwin)
GOBIN := $(shell echo ${GOBIN} | cut -d':' -f1) GOBIN := $(shell echo ${GOBIN} | cut -d':' -f1)
GOPATH := $(shell echo $(GOPATH) | 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 := "" BIN := ""
TOOLS_SHELL="./hack/tools.sh" TOOLS_SHELL="./hack/tools.sh"
@ -14,7 +32,7 @@ LINTER := bin/golangci-lint
ifneq ($(GOBIN),) ifneq ($(GOBIN),)
BIN=$(GOBIN) BIN=$(GOBIN)
else else
# check GOPATH # check GOPATH
ifneq ($(GOPATH),) ifneq ($(GOPATH),)
BIN=$(GOPATH)/bin BIN=$(GOPATH)/bin
endif endif
@ -37,9 +55,9 @@ ifeq ($(user),root)
@cp ./cmd/protoc-gen-go-http/protoc-gen-go-http /usr/bin @cp ./cmd/protoc-gen-go-http/protoc-gen-go-http /usr/bin
else else
#!root, install for current user #!root, install for current user
$(shell if [ -z $(BIN) ]; then read -p "Please select installdir: " REPLY; mkdir -p $${REPLY};\ $(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 $${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) 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 endif
@which protoc-gen-go &> /dev/null || go get google.golang.org/protobuf/cmd/protoc-gen-go @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 @which protoc-gen-go-grpc &> /dev/null || go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
@ -77,3 +95,8 @@ test-coverage:
lint: $(LINTER) lint: $(LINTER)
@${TOOLS_SHELL} lint @${TOOLS_SHELL} lint
@echo "lint check finished" @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 ## About Kratos
> 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. > 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.
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: 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. - 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 are support: [HTTP](https://go-kratos.dev/en/docs/component/transport/http) / [gRPC](https://go-kratos.dev/en/docs/component/transport/grpc). - 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. - 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. - [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 and logs are collected through the *Fluentd*. - 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*.
- The selection of the content [encoding](https://go-kratos.dev/en/docs/component/encoding) is automatically supported by Accept and Content-Type. - Automatically support the selection of the content [encoding](https://go-kratos.dev/en/docs/component/encoding) with 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). - 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 uniform [metadata](https://go-kratos.dev/en/docs/component/metadata) transfer method. - In the protocol of HTTP/gRPC, use the 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 [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 which is supported by HTTP/gRPC service. - 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) generated Automatically and embed Swagger UI endpoint can be started by adding [Swagger plugin](https://github.com/go-kratos/swagger-api). - [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).
Kratos is accessible, powerful, and provides tools required for large, robust applications. 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. 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 a modern project. 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.
### Goals ### Goals
@ -47,13 +47,13 @@ Kratos boosts your productivity. With the integration of excellent resources and
### Principles ### Principles
* **Simple**: Appropriate design, plain and easy code. * **Simple**: Appropriate design with plain and easy code.
* **General**: Cover the various utilities for business development. * **General**: Cover the various utilities for business development.
* **Highly efficient**: Speeding up the efficiency of businesses upgrading. * **Highly efficient**: Speeding up the efficiency of businesses upgrading.
* **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. * **Stable**: The base libs validated in the production environment have the characteristics of high testability, high coverage as well as high security and reliability.
* **Robust**: Eliminating misusing through high quality of the base libs. * **Robust**: Eliminating misusing through high quality of the base libs.
* **High-performance**: Optimal performance excluding the optimization of hacking in case of *unsafe*.  * **High-performance**: Optimal performance excluding the optimization of hacking in case of *unsafe*. 
* **Expandability**: Properly designed interfaces, you can expand utilities such as base libs to meet your further requirements. * **Expandability**: Properly designed interfaces where 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. * **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. * **Toolchain**: Includes an extensive toolchain, such as the code generation of cache, the lint tool, and so forth.
@ -90,7 +90,6 @@ If you discover a security vulnerability within Kratos, please send an e-mail to
- [Wechat Group](https://github.com/go-kratos/kratos/issues/682) - [Wechat Group](https://github.com/go-kratos/kratos/issues/682)
- [Discord Group](https://discord.gg/BWzJsUJ) - [Discord Group](https://discord.gg/BWzJsUJ)
- [go-kratos.dev](https://go-kratos.dev/en) - [go-kratos.dev](https://go-kratos.dev/en)
- QQ Group: 716486124
## Contributors ## Contributors
@ -111,5 +110,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. - [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. - [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. - [google/go-cloud](https://github.com/google/go-cloud) is go cloud development kit.
- [tal-tech/go-zero](https://github.com/tal-tech/go-zero) is a web and rpc framework with lots of builtin engineering practices. - [zeromicro/go-zero](https://github.com/zeromicro/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. - [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 工具等等; * 工具链:包含大量工具链,比如 cache 代码生成,lint 工具等等;
## Features ## Features
* [APIs](https://github.com/go-kratos/examples/helloworld/helloworld) :协议通信以 HTTP/gRPC 为基础,通过 Protobuf 进行定义; * [APIs](https://go-kratos.dev/docs/component/api) :协议通信以 HTTP/gRPC 为基础,通过 Protobuf 进行定义;
* [Errors](https://github.com/go-kratos/examples/errors/api) :通过 Protobuf 的 Enum 作为错误码定义,以及工具生成判定接口; * [Errors](https://go-kratos.dev/docs/component/errors/) :通过 Protobuf 的 Enum 作为错误码定义,以及工具生成判定接口;
* [Metadata](https://github.com/go-kratos/examples/metadata) :在协议通信 HTTP/gRPC 中,通过 Middleware 规范化服务元信息传递; * [Metadata](https://go-kratos.dev/docs/component/metadata) :在协议通信 HTTP/gRPC 中,通过 Middleware 规范化服务元信息传递;
* [Config](https://github.com/go-kratos/examples/config) :支持多数据源方式,进行配置合并铺平,通过 Atomic 方式支持动态配置; * [Config](https://go-kratos.dev/docs/component/config) :支持多数据源方式,进行配置合并铺平,通过 Atomic 方式支持动态配置;
* [Logger](https://github.com/go-kratos/examples/log) :标准日志接口,可方便集成三方 log 库,并可通过 fluentd 收集日志; * [Logger](https://go-kratos.dev/docs/component/log) :标准日志接口,可方便集成三方 log 库,并可通过 fluentd 收集日志;
* [Metrics](https://github.com/go-kratos/examples/metrics) :统一指标接口,可以实现各种指标系统,默认集成 Prometheus; * [Metrics](https://go-kratos.dev/docs/component/middleware/metrics) :统一指标接口,可以实现各种指标系统,默认集成 Prometheus;
* [Tracing](https://github.com/go-kratos/examples/traces) :遵循 OpenTelemetry 规范定义,以实现微服务链路追踪; * [Tracing](https://go-kratos.dev/docs/component/middleware/tracing) :遵循 OpenTelemetry 规范定义,以实现微服务链路追踪;
* [Encoding](encoding):支持 Accept 和 Content-Type 进行自动选择内容编码; * [Encoding](https://go-kratos.dev/docs/component/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) 插件支持; * [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://github.com/go-kratos/examples/registry) :实现统一注册中心接口,可插件化对接各种注册中心; * [Registry](https://go-kratos.dev/docs/component/registry) :实现统一注册中心接口,可插件化对接各种注册中心;
* [Validation](https://github.com/go-kratos/examples/validate): 通过Protobuf统一定义校验规则,并同时适用于HTTP/gRPC服务. * [Validation](https://go-kratos.dev/docs/component/middleware/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服务. * [SwaggerAPI](https://go-kratos.dev/docs/guide/openapi): 通过集成第三方[Swagger插件](https://github.com/go-kratos/swagger-api) 能够自动生成Swagger API json并启动一个内置的Swagger UI服务.
## Getting Started ## Getting Started
### Required ### Required

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

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

@ -1,4 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // 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 package metadata
@ -137,5 +141,5 @@ var Metadata_ServiceDesc = grpc.ServiceDesc{
}, },
}, },
Streams: []grpc.StreamDesc{}, Streams: []grpc.StreamDesc{},
Metadata: "metadata.proto", Metadata: "metadata/metadata.proto",
} }

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

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

@ -9,7 +9,6 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/go-kratos/kratos/v2/health"
"github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/registry"
"github.com/go-kratos/kratos/v2/transport" "github.com/go-kratos/kratos/v2/transport"
@ -25,7 +24,6 @@ type AppInfo interface {
Version() string Version() string
Metadata() map[string]string Metadata() map[string]string
Endpoint() []string Endpoint() []string
Health() *health.Health
} }
// App is an application components lifecycle manager. // App is an application components lifecycle manager.
@ -33,16 +31,14 @@ type App struct {
opts options opts options
ctx context.Context ctx context.Context
cancel func() cancel func()
lk sync.Mutex mu sync.Mutex
instance *registry.ServiceInstance instance *registry.ServiceInstance
health *health.Health
} }
// New create an application lifecycle manager. // New create an application lifecycle manager.
func New(opts ...Option) *App { func New(opts ...Option) *App {
o := options{ o := options{
ctx: context.Background(), ctx: context.Background(),
logger: log.NewHelper(log.GetLogger()),
sigs: []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT}, sigs: []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT},
registrarTimeout: 10 * time.Second, registrarTimeout: 10 * time.Second,
stopTimeout: 10 * time.Second, stopTimeout: 10 * time.Second,
@ -53,12 +49,14 @@ func New(opts ...Option) *App {
for _, opt := range opts { for _, opt := range opts {
opt(&o) opt(&o)
} }
if o.logger != nil {
log.SetLogger(o.logger)
}
ctx, cancel := context.WithCancel(o.ctx) ctx, cancel := context.WithCancel(o.ctx)
return &App{ return &App{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
opts: o, opts: o,
health: health.New(),
} }
} }
@ -76,23 +74,30 @@ func (a *App) Metadata() map[string]string { return a.opts.metadata }
// Endpoint returns endpoints. // Endpoint returns endpoints.
func (a *App) Endpoint() []string { func (a *App) Endpoint() []string {
if a.instance == nil { if a.instance != nil {
return []string{} return a.instance.Endpoints
} }
return a.instance.Endpoints return nil
} }
// Health returns health
func (a *App) Health() *health.Health { return a.health }
// Run executes all OnStart hooks registered with the application's Lifecycle. // Run executes all OnStart hooks registered with the application's Lifecycle.
func (a *App) Run() error { func (a *App) Run() error {
instance, err := a.buildInstance() instance, err := a.buildInstance()
if err != nil { if err != nil {
return err return err
} }
eg, ctx := errgroup.WithContext(NewContext(a.ctx, a)) a.mu.Lock()
a.instance = instance
a.mu.Unlock()
sctx := NewContext(a.ctx, a)
eg, ctx := errgroup.WithContext(sctx)
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
for _, fn := range a.opts.beforeStart {
if err = fn(sctx); err != nil {
return err
}
}
for _, srv := range a.opts.servers { for _, srv := range a.opts.servers {
srv := srv srv := srv
eg.Go(func() error { eg.Go(func() error {
@ -103,70 +108,64 @@ func (a *App) Run() error {
}) })
wg.Add(1) wg.Add(1)
eg.Go(func() error { eg.Go(func() error {
wg.Done() wg.Done() // here is to ensure server start has begun running before register, so defer is not needed
return srv.Start(NewContext(a.opts.ctx, a)) return srv.Start(sctx)
}) })
} }
wg.Wait() wg.Wait()
err = a.health.SetStatus(a.Name(), health.Status_SERVING)
if err != nil {
log.Errorf("set health status error: %v", err)
}
if a.opts.registrar != nil { if a.opts.registrar != nil {
rctx, rcancel := context.WithTimeout(ctx, a.opts.registrarTimeout) rctx, rcancel := context.WithTimeout(ctx, a.opts.registrarTimeout)
defer rcancel() defer rcancel()
if err = a.opts.registrar.Register(rctx, instance); err != nil { if err = a.opts.registrar.Register(rctx, instance); err != nil {
return err return err
} }
a.lk.Lock()
a.instance = instance
a.lk.Unlock()
} }
for _, fn := range a.opts.afterStart {
if err = fn(sctx); err != nil {
return err
}
}
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, a.opts.sigs...) signal.Notify(c, a.opts.sigs...)
eg.Go(func() error { eg.Go(func() error {
for { select {
select { case <-ctx.Done():
case <-ctx.Done(): return nil
err = a.health.SetStatus(a.Name(), health.Status_NOT_SERVING) case <-c:
if err != nil { return a.Stop()
log.Errorf("set health status error: %v", err)
}
return ctx.Err()
case <-c:
if err := a.Stop(); err != nil {
a.opts.logger.Errorf("failed to stop app: %v", err)
return err
}
}
} }
}) })
if err := eg.Wait(); err != nil && !errors.Is(err, context.Canceled) { if err = eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
return err return err
} }
return nil for _, fn := range a.opts.afterStop {
err = fn(sctx)
}
return err
} }
// Stop gracefully stops the application. // Stop gracefully stops the application.
func (a *App) Stop() error { func (a *App) Stop() (err error) {
err := a.health.SetStatus(a.Name(), health.Status_NOT_SERVING) sctx := NewContext(a.ctx, a)
if err != nil { for _, fn := range a.opts.beforeStop {
log.Errorf("set health status error: %v", err) err = fn(sctx)
} }
a.lk.Lock()
a.mu.Lock()
instance := a.instance instance := a.instance
a.lk.Unlock() a.mu.Unlock()
if a.opts.registrar != nil && instance != nil { if a.opts.registrar != nil && instance != nil {
ctx, cancel := context.WithTimeout(NewContext(a.ctx, a), a.opts.registrarTimeout) ctx, cancel := context.WithTimeout(NewContext(a.ctx, a), a.opts.registrarTimeout)
defer cancel() defer cancel()
if err := a.opts.registrar.Deregister(ctx, instance); err != nil { if err = a.opts.registrar.Deregister(ctx, instance); err != nil {
return err return err
} }
} }
if a.cancel != nil { if a.cancel != nil {
a.cancel() a.cancel()
} }
return nil return err
} }
func (a *App) buildInstance() (*registry.ServiceInstance, error) { func (a *App) buildInstance() (*registry.ServiceInstance, error) {

@ -2,13 +2,16 @@ package kratos
import ( import (
"context" "context"
"fmt" "errors"
"net/url"
"reflect" "reflect"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/go-kratos/kratos/v2/registry" "github.com/go-kratos/kratos/v2/registry"
"github.com/go-kratos/kratos/v2/transport/grpc"
"github.com/go-kratos/kratos/v2/transport/http"
) )
type mockRegistry struct { type mockRegistry struct {
@ -16,9 +19,9 @@ type mockRegistry struct {
service map[string]*registry.ServiceInstance service map[string]*registry.ServiceInstance
} }
func (r *mockRegistry) Register(ctx context.Context, service *registry.ServiceInstance) error { func (r *mockRegistry) Register(_ context.Context, service *registry.ServiceInstance) error {
if service == nil || service.ID == "" { if service == nil || service.ID == "" {
return fmt.Errorf("no service id") return errors.New("no service id")
} }
r.lk.Lock() r.lk.Lock()
defer r.lk.Unlock() defer r.lk.Unlock()
@ -27,23 +30,39 @@ func (r *mockRegistry) Register(ctx context.Context, service *registry.ServiceIn
} }
// Deregister the registration. // Deregister the registration.
func (r *mockRegistry) Deregister(ctx context.Context, service *registry.ServiceInstance) error { func (r *mockRegistry) Deregister(_ context.Context, service *registry.ServiceInstance) error {
r.lk.Lock() r.lk.Lock()
defer r.lk.Unlock() defer r.lk.Unlock()
if r.service[service.ID] == nil { if r.service[service.ID] == nil {
return fmt.Errorf("deregister service not found") return errors.New("deregister service not found")
} }
delete(r.service, service.ID) delete(r.service, service.ID)
return nil return nil
} }
func TestApp(t *testing.T) { func TestApp(t *testing.T) {
// hs := http.NewServer() hs := http.NewServer()
// gs := grpc.NewServer() gs := grpc.NewServer()
app := New( app := New(
Name("kratos"), Name("kratos"),
Version("v1.0.0"), Version("v1.0.0"),
// Server(hs, gs), 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)}), Registrar(&mockRegistry{service: make(map[string]*registry.ServiceInstance)}),
) )
time.AfterFunc(time.Second, func() { time.AfterFunc(time.Second, func() {
@ -90,6 +109,80 @@ func TestApp_Metadata(t *testing.T) {
} }
func TestApp_Endpoint(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 { type fields struct {
id string id string
version string version string
@ -152,7 +245,7 @@ func TestApp_Endpoint(t *testing.T) {
endpoint []string endpoint []string
metadata map[string]string metadata map[string]string
}{ }{
id: "3", version: "v3", name: "kratos-v3", endpoint: []string{}, id: "3", version: "v3", name: "kratos-v3", endpoint: nil,
metadata: map[string]string{}, metadata: map[string]string{},
}, },
}, },

@ -3,15 +3,13 @@ module github.com/go-kratos/kratos/cmd/kratos/v2
go 1.16 go 1.16
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.4 github.com/AlecAivazis/survey/v2 v2.3.7
github.com/emicklei/proto v1.10.0 github.com/emicklei/proto v1.10.0
github.com/fatih/color v1.13.0 github.com/fatih/color v1.13.0
github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-colorable v0.1.12 // indirect
github.com/spf13/cobra v1.4.0 github.com/spf13/cobra v1.4.0
github.com/stretchr/testify v1.7.0 // indirect github.com/stretchr/testify v1.7.0 // indirect
golang.org/x/mod v0.5.1 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect golang.org/x/text v0.4.0
golang.org/x/text v0.3.7 gopkg.in/yaml.v3 v3.0.0 // indirect
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.4 h1:pchTU9rsLUSvWEl2Aq9Pv3k0IE2fkqtGxazskAMd9Ng= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.4/go.mod h1:hrV6Y/kQCLhIZXGcriDCUBtB3wnN7156gMXJ3+b23xM= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= 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/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= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@ -39,37 +39,41 @@ 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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-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-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-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-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-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-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-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-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-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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.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-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.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-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/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.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-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -1,6 +1,9 @@
package base package base
import "testing" import (
"os"
"testing"
)
func TestModuleVersion(t *testing.T) { func TestModuleVersion(t *testing.T) {
v, err := ModuleVersion("golang.org/x/mod") v, err := ModuleVersion("golang.org/x/mod")
@ -9,3 +12,32 @@ func TestModuleVersion(t *testing.T) {
} }
t.Log(v) 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,7 +5,6 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -17,7 +16,7 @@ func kratosHome() string {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
home := path.Join(dir, ".kratos") home := filepath.Join(dir, ".kratos")
if _, err := os.Stat(home); os.IsNotExist(err) { if _, err := os.Stat(home); os.IsNotExist(err) {
if err := os.MkdirAll(home, 0o700); err != nil { if err := os.MkdirAll(home, 0o700); err != nil {
log.Fatal(err) log.Fatal(err)
@ -27,7 +26,7 @@ func kratosHome() string {
} }
func kratosHomeWithDir(dir string) string { func kratosHomeWithDir(dir string) string {
home := path.Join(kratosHome(), dir) home := filepath.Join(kratosHome(), dir)
if _, err := os.Stat(home); os.IsNotExist(err) { if _, err := os.Stat(home); os.IsNotExist(err) {
if err := os.MkdirAll(home, 0o700); err != nil { if err := os.MkdirAll(home, 0o700); err != nil {
log.Fatal(err) log.Fatal(err)
@ -37,7 +36,6 @@ func kratosHomeWithDir(dir string) string {
} }
func copyFile(src, dst string, replaces []string) error { func copyFile(src, dst string, replaces []string) error {
var err error
srcinfo, err := os.Stat(src) srcinfo, err := os.Stat(src)
if err != nil { if err != nil {
return err return err
@ -58,27 +56,26 @@ func copyFile(src, dst string, replaces []string) error {
} }
func copyDir(src, dst string, replaces, ignores []string) error { func copyDir(src, dst string, replaces, ignores []string) error {
var err error srcinfo, err := os.Stat(src)
var fds []os.DirEntry if err != nil {
var srcinfo os.FileInfo
if srcinfo, err = os.Stat(src); err != nil {
return err return err
} }
if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil { err = os.MkdirAll(dst, srcinfo.Mode())
if err != nil {
return err return err
} }
if fds, err = os.ReadDir(src); err != nil { fds, err := os.ReadDir(src)
if err != nil {
return err return err
} }
for _, fd := range fds { for _, fd := range fds {
if hasSets(fd.Name(), ignores) { if hasSets(fd.Name(), ignores) {
continue continue
} }
srcfp := path.Join(src, fd.Name()) srcfp := filepath.Join(src, fd.Name())
dstfp := path.Join(dst, fd.Name()) dstfp := filepath.Join(dst, fd.Name())
var e error var e error
if fd.IsDir() { if fd.IsDir() {
e = copyDir(srcfp, dstfp, replaces, ignores) e = copyDir(srcfp, dstfp, replaces, ignores)

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

@ -7,7 +7,37 @@ import (
) )
func TestRepo(t *testing.T) { func TestRepo(t *testing.T) {
os.RemoveAll("/tmp/test_repo") 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) {
r := NewRepo("https://github.com/go-kratos/service-layout.git", "") r := NewRepo("https://github.com/go-kratos/service-layout.git", "")
if err := r.Clone(context.Background()); err != nil { if err := r.Clone(context.Background()); err != nil {
t.Fatal(err) t.Fatal(err)
@ -15,19 +45,7 @@ func TestRepo(t *testing.T) {
if err := r.CopyTo(context.Background(), "/tmp/test_repo", "github.com/go-kratos/kratos-layout", nil); err != nil { if err := r.CopyTo(context.Background(), "/tmp/test_repo", "github.com/go-kratos/kratos-layout", nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
urls := []string{ t.Cleanup(func() {
"ssh://git@gitlab.xxx.com:1234/foo/bar.git", os.RemoveAll("/tmp/test_repo")
"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)
}
}
} }

@ -0,0 +1,58 @@
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")
}

@ -0,0 +1,55 @@
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") token = os.Getenv("GITHUB_TOKEN")
} }
func run(cmd *cobra.Command, args []string) { func run(_ *cobra.Command, args []string) {
owner, repo := ParseGithubURL(repoURL) owner, repo := ParseGithubURL(repoURL)
api := GithubAPI{Owner: owner, Repo: repo, Token: token} api := GithubAPI{Owner: owner, Repo: repo, Token: token}
version := "latest" version := "latest"

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

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

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

@ -5,12 +5,14 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"path" "path/filepath"
"strings"
"time" "time"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/go-kratos/kratos/cmd/kratos/v2/internal/base"
) )
// CmdNew represents the new command. // CmdNew represents the new command.
@ -39,7 +41,7 @@ func init() {
CmdNew.Flags().BoolVarP(&nomod, "nomod", "", nomod, "retain go mod") CmdNew.Flags().BoolVarP(&nomod, "nomod", "", nomod, "retain go mod")
} }
func run(cmd *cobra.Command, args []string) { func run(_ *cobra.Command, args []string) {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
panic(err) panic(err)
@ -63,23 +65,34 @@ func run(cmd *cobra.Command, args []string) {
} else { } else {
name = args[0] name = args[0]
} }
p := &Project{Name: path.Base(name), Path: name} projectName, workingDir := processProjectParams(name, wd)
p := &Project{Name: projectName}
done := make(chan error, 1) done := make(chan error, 1)
go func() { go func() {
if !nomod { if !nomod {
done <- p.New(ctx, wd, repoURL, branch) done <- p.New(ctx, workingDir, repoURL, branch)
return return
} }
if _, e := os.Stat(path.Join(wd, "go.mod")); os.IsNotExist(e) { projectRoot := getgomodProjectRoot(workingDir)
done <- fmt.Errorf("🚫 go.mod don't exists in %s", wd) 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)
return return
} }
mod, e := base.ModulePath(path.Join(wd, "go.mod")) mod, e := base.ModulePath(filepath.Join(projectRoot, "go.mod"))
if e != nil { if e != nil {
panic(e) done <- fmt.Errorf("🚫 failed to parse `go.mod`: %v", e)
return
} }
done <- p.Add(ctx, wd, repoURL, branch, mod) // 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)
}() }()
select { select {
case <-ctx.Done(): case <-ctx.Done():
@ -94,3 +107,43 @@ func run(cmd *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)
}

@ -0,0 +1,29 @@
//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)
}
})
}
}

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

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,17 @@
{{ 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 go 1.16
require ( require (
golang.org/x/text v0.3.7 golang.org/x/text v0.3.8
google.golang.org/protobuf v1.28.0 google.golang.org/protobuf v1.28.0
) )

@ -1,9 +1,31 @@
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 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 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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-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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=

@ -2,28 +2,12 @@ package main
import ( import (
"bytes" "bytes"
_ "embed"
"text/template" "text/template"
) )
var errorsTemplate = ` //go:embed errorsTemplate.tpl
{{ range .Errors }} var errorsTemplate string
{{ 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 { type errorInfo struct {
Name string Name string

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

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

@ -0,0 +1,93 @@
{{$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}}

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

@ -2,97 +2,13 @@ package main
import ( import (
"bytes" "bytes"
_ "embed"
"strings" "strings"
"text/template" "text/template"
) )
var httpTemplate = ` //go:embed httpTemplate.tpl
{{$svrType := .ServiceType}} var httpTemplate string
{{$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 { type serviceDesc struct {
ServiceType string // Greeter ServiceType string // Greeter
@ -109,6 +25,7 @@ type methodDesc struct {
Num int Num int
Request string Request string
Reply string Reply string
Comment string
// http_rule // http_rule
Path string Path string
Method string Method string

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

@ -7,22 +7,21 @@ import (
"sync" "sync"
"time" "time"
"github.com/go-kratos/kratos/v2/log"
// init encoding // init encoding
_ "github.com/go-kratos/kratos/v2/encoding/json" _ "github.com/go-kratos/kratos/v2/encoding/json"
_ "github.com/go-kratos/kratos/v2/encoding/proto" _ "github.com/go-kratos/kratos/v2/encoding/proto"
_ "github.com/go-kratos/kratos/v2/encoding/xml" _ "github.com/go-kratos/kratos/v2/encoding/xml"
_ "github.com/go-kratos/kratos/v2/encoding/yaml" _ "github.com/go-kratos/kratos/v2/encoding/yaml"
"github.com/go-kratos/kratos/v2/log"
) )
var _ Config = (*config)(nil)
var ( var (
// ErrNotFound is key not found. // ErrNotFound is key not found.
ErrNotFound = errors.New("key not found") ErrNotFound = errors.New("key not found")
// ErrTypeAssert is type assert error. // ErrTypeAssert is type assert error.
ErrTypeAssert = errors.New("type assert error") ErrTypeAssert = errors.New("type assert error")
_ Config = (*config)(nil)
) )
// Observer is config observer. // Observer is config observer.
@ -43,13 +42,11 @@ type config struct {
cached sync.Map cached sync.Map
observers sync.Map observers sync.Map
watchers []Watcher watchers []Watcher
log *log.Helper
} }
// New new a config with options. // New a config with options.
func New(opts ...Option) Config { func New(opts ...Option) Config {
o := options{ o := options{
logger: log.GetLogger(),
decoder: defaultDecoder, decoder: defaultDecoder,
resolver: defaultResolver, resolver: defaultResolver,
} }
@ -59,28 +56,27 @@ func New(opts ...Option) Config {
return &config{ return &config{
opts: o, opts: o,
reader: newReader(o), reader: newReader(o),
log: log.NewHelper(o.logger),
} }
} }
func (c *config) watch(w Watcher) { func (c *config) watch(w Watcher) {
for { for {
kvs, err := w.Next() kvs, err := w.Next()
if errors.Is(err, context.Canceled) {
c.log.Infof("watcher's ctx cancel : %v", err)
return
}
if err != nil { if err != nil {
if errors.Is(err, context.Canceled) {
log.Infof("watcher's ctx cancel : %v", err)
return
}
time.Sleep(time.Second) time.Sleep(time.Second)
c.log.Errorf("failed to watch next config: %v", err) log.Errorf("failed to watch next config: %v", err)
continue continue
} }
if err := c.reader.Merge(kvs...); err != nil { if err := c.reader.Merge(kvs...); err != nil {
c.log.Errorf("failed to merge next config: %v", err) log.Errorf("failed to merge next config: %v", err)
continue continue
} }
if err := c.reader.Resolve(); err != nil { if err := c.reader.Resolve(); err != nil {
c.log.Errorf("failed to resolve next config: %v", err) log.Errorf("failed to resolve next config: %v", err)
continue continue
} }
c.cached.Range(func(key, value interface{}) bool { c.cached.Range(func(key, value interface{}) bool {
@ -104,22 +100,22 @@ func (c *config) Load() error {
return err return err
} }
for _, v := range kvs { for _, v := range kvs {
c.log.Debugf("config loaded: %s format: %s", v.Key, v.Format) log.Debugf("config loaded: %s format: %s", v.Key, v.Format)
} }
if err = c.reader.Merge(kvs...); err != nil { if err = c.reader.Merge(kvs...); err != nil {
c.log.Errorf("failed to merge config source: %v", err) log.Errorf("failed to merge config source: %v", err)
return err return err
} }
w, err := src.Watch() w, err := src.Watch()
if err != nil { if err != nil {
c.log.Errorf("failed to watch config source: %v", err) log.Errorf("failed to watch config source: %v", err)
return err return err
} }
c.watchers = append(c.watchers, w) c.watchers = append(c.watchers, w)
go c.watch(w) go c.watch(w)
} }
if err := c.reader.Resolve(); err != nil { if err := c.reader.Resolve(); err != nil {
c.log.Errorf("failed to resolve config source: %v", err) log.Errorf("failed to resolve config source: %v", err)
return err return err
} }
return nil return nil

@ -2,10 +2,7 @@ package config
import ( import (
"errors" "errors"
"reflect"
"testing" "testing"
"github.com/go-kratos/kratos/v2/log"
) )
const ( const (
@ -123,11 +120,10 @@ func TestConfig(t *testing.T) {
WithSource(newTestJSONSource(_testJSON)), WithSource(newTestJSONSource(_testJSON)),
WithDecoder(defaultDecoder), WithDecoder(defaultDecoder),
WithResolver(defaultResolver), WithResolver(defaultResolver),
WithLogger(log.GetLogger()),
) )
err = c.Close() err = c.Close()
if err != nil { if err != nil {
t.Fatal("t is not nil") t.Fatal(err)
} }
jSource := newTestJSONSource(_testJSON) jSource := newTestJSONSource(_testJSON)
@ -135,30 +131,28 @@ func TestConfig(t *testing.T) {
sources: []Source{jSource}, sources: []Source{jSource},
decoder: defaultDecoder, decoder: defaultDecoder,
resolver: defaultResolver, resolver: defaultResolver,
logger: log.GetLogger(),
} }
cf := &config{} cf := &config{}
cf.opts = opts cf.opts = opts
cf.reader = newReader(opts) cf.reader = newReader(opts)
cf.log = log.NewHelper(opts.logger)
err = cf.Load() err = cf.Load()
if err != nil { if err != nil {
t.Fatal("t is not nil") t.Fatal(err)
} }
val, err := cf.Value("data.database.driver").String() driver, err := cf.Value("data.database.driver").String()
if err != nil { if err != nil {
t.Fatal("t is not nil") t.Fatal(err)
} }
if !reflect.DeepEqual(databaseDriver, val) { if databaseDriver != driver {
t.Fatal(`databaseDriver is not equal to val`) t.Fatal("databaseDriver is not equal to val")
} }
err = cf.Watch("endpoints", func(key string, value Value) { err = cf.Watch("endpoints", func(key string, value Value) {
}) })
if err != nil { if err != nil {
t.Fatal("t is not nil") t.Fatal(err)
} }
jSource.sig <- struct{}{} jSource.sig <- struct{}{}
@ -167,24 +161,24 @@ func TestConfig(t *testing.T) {
var testConf testConfigStruct var testConf testConfigStruct
err = cf.Scan(&testConf) err = cf.Scan(&testConf)
if err != nil { if err != nil {
t.Fatal("t is not nil") t.Fatal(err)
} }
if !reflect.DeepEqual(httpAddr, testConf.Server.HTTP.Addr) { if httpAddr != testConf.Server.HTTP.Addr {
t.Fatal(`httpAddr is not equal to testConf.Server.HTTP.Addr`) t.Errorf("testConf.Server.HTTP.Addr want: %s, got: %s", httpAddr, testConf.Server.HTTP.Addr)
} }
if !reflect.DeepEqual(httpTimeout, testConf.Server.HTTP.Timeout) { if httpTimeout != testConf.Server.HTTP.Timeout {
t.Fatal(`httpTimeout is not equal to testConf.Server.HTTP.Timeout`) t.Errorf("testConf.Server.HTTP.Timeout want: %.1f, got: %.1f", httpTimeout, testConf.Server.HTTP.Timeout)
} }
if !reflect.DeepEqual(true, testConf.Server.HTTP.EnableSSL) { if !testConf.Server.HTTP.EnableSSL {
t.Fatal(`testConf.Server.HTTP.EnableSSL is not equal to true`) t.Error("testConf.Server.HTTP.EnableSSL is not equal to true")
} }
if !reflect.DeepEqual(grpcPort, testConf.Server.GRPC.Port) { if grpcPort != testConf.Server.GRPC.Port {
t.Fatal(`grpcPort is not equal to testConf.Server.GRPC.Port`) t.Errorf("testConf.Server.GRPC.Port want: %d, got: %d", grpcPort, testConf.Server.GRPC.Port)
} }
if !reflect.DeepEqual(endpoint1, testConf.Endpoints[0]) { if endpoint1 != testConf.Endpoints[0] {
t.Fatal(`endpoint1 is not equal to testConf.Endpoints[0]`) t.Errorf("testConf.Endpoints[0] want: %s, got: %s", endpoint1, testConf.Endpoints[0])
} }
if !reflect.DeepEqual(len(testConf.Endpoints), 2) { if len(testConf.Endpoints) != 2 {
t.Fatal(`len(testConf.Endpoints) is not equal to 2`) t.Error("len(testConf.Endpoints) is not equal to 2")
} }
} }

16
config/env/env.go vendored

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

@ -261,7 +261,7 @@ func TestEnvWithoutPrefix(t *testing.T) {
func Test_env_load(t *testing.T) { func Test_env_load(t *testing.T) {
type fields struct { type fields struct {
prefixs []string prefixes []string
} }
type args struct { type args struct {
envStrings []string envStrings []string
@ -275,7 +275,7 @@ func Test_env_load(t *testing.T) {
{ {
name: "without prefixes", name: "without prefixes",
fields: fields{ fields: fields{
prefixs: nil, prefixes: nil,
}, },
args: args{ args: args{
envStrings: []string{ envStrings: []string{
@ -294,7 +294,7 @@ func Test_env_load(t *testing.T) {
{ {
name: "empty prefix", name: "empty prefix",
fields: fields{ fields: fields{
prefixs: []string{""}, prefixes: []string{""},
}, },
args: args{ args: args{
envStrings: []string{ envStrings: []string{
@ -313,7 +313,7 @@ func Test_env_load(t *testing.T) {
{ {
name: "underscore prefix", name: "underscore prefix",
fields: fields{ fields: fields{
prefixs: []string{"_"}, prefixes: []string{"_"},
}, },
args: args{ args: args{
envStrings: []string{ envStrings: []string{
@ -332,7 +332,7 @@ func Test_env_load(t *testing.T) {
{ {
name: "with prefixes", name: "with prefixes",
fields: fields{ fields: fields{
prefixs: []string{"KRATOS_", "FOO"}, prefixes: []string{"KRATOS_", "FOO"},
}, },
args: args{ args: args{
envStrings: []string{ envStrings: []string{
@ -351,7 +351,7 @@ func Test_env_load(t *testing.T) {
{ {
name: "should not panic #1", name: "should not panic #1",
fields: fields{ fields: fields{
prefixs: []string{"FOO"}, prefixes: []string{"FOO"},
}, },
args: args{ args: args{
envStrings: []string{ envStrings: []string{
@ -364,7 +364,7 @@ func Test_env_load(t *testing.T) {
{ {
name: "should not panic #2", name: "should not panic #2",
fields: fields{ fields: fields{
prefixs: []string{"FOO=1"}, prefixes: []string{"FOO=1"},
}, },
args: args{ args: args{
envStrings: []string{ envStrings: []string{
@ -377,7 +377,7 @@ func Test_env_load(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
e := &env{ e := &env{
prefixs: tt.fields.prefixs, prefixes: tt.fields.prefixes,
} }
got := e.load(tt.args.envStrings) got := e.load(tt.args.envStrings)
if !reflect.DeepEqual(tt.want, got) { if !reflect.DeepEqual(tt.want, got) {
@ -417,3 +417,13 @@ 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" "github.com/go-kratos/kratos/v2/config"
) )
var _ config.Watcher = (*watcher)(nil)
type watcher struct { type watcher struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
} }
var _ config.Watcher = (*watcher)(nil)
func NewWatcher() (config.Watcher, error) { func NewWatcher() (config.Watcher, error) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return &watcher{ctx: ctx, cancel: cancel}, nil return &watcher{ctx: ctx, cancel: cancel}, nil

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

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

@ -22,7 +22,6 @@ type options struct {
sources []Source sources []Source
decoder Decoder decoder Decoder
resolver Resolver resolver Resolver
logger log.Logger
} }
// WithSource with config source. // WithSource with config source.
@ -51,10 +50,9 @@ func WithResolver(r Resolver) Option {
} }
// WithLogger with config logger. // WithLogger with config logger.
func WithLogger(l log.Logger) Option { // Deprecated: use global logger instead.
return func(o *options) { func WithLogger(_ log.Logger) Option {
o.logger = l return func(o *options) {}
}
} }
// defaultDecoder decode config from source KeyValue // defaultDecoder decode config from source KeyValue

@ -2,6 +2,7 @@ package config
import ( import (
"reflect" "reflect"
"strings"
"testing" "testing"
) )
@ -14,7 +15,7 @@ func TestDefaultDecoder(t *testing.T) {
target := make(map[string]interface{}) target := make(map[string]interface{})
err := defaultDecoder(src, target) err := defaultDecoder(src, target)
if err != nil { if err != nil {
t.Fatal("err is not nil") t.Fatal(err)
} }
if !reflect.DeepEqual(target, map[string]interface{}{"service": []byte("config")}) { if !reflect.DeepEqual(target, map[string]interface{}{"service": []byte("config")}) {
t.Fatal(`target is not equal to map[string]interface{}{"service": "config"}`) t.Fatal(`target is not equal to map[string]interface{}{"service": "config"}`)
@ -28,7 +29,7 @@ func TestDefaultDecoder(t *testing.T) {
target = make(map[string]interface{}) target = make(map[string]interface{})
err = defaultDecoder(src, target) err = defaultDecoder(src, target)
if err != nil { if err != nil {
t.Fatal("err is not nil") t.Fatal(err)
} }
if !reflect.DeepEqual(map[string]interface{}{ if !reflect.DeepEqual(map[string]interface{}{
"service": map[string]interface{}{ "service": map[string]interface{}{
@ -149,7 +150,7 @@ func TestDefaultResolver(t *testing.T) {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
err := defaultResolver(data) err := defaultResolver(data)
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
rd := reader{ rd := reader{
values: data, values: data,
@ -160,25 +161,25 @@ func TestDefaultResolver(t *testing.T) {
case int: case int:
if actual, err = v.Int(); err == nil { if actual, err = v.Int(); err == nil {
if !reflect.DeepEqual(test.expect.(int), int(actual.(int64))) { 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: case string:
if actual, err = v.String(); err == nil { if actual, err = v.String(); err == nil {
if !reflect.DeepEqual(test.expect, actual) { if !reflect.DeepEqual(test.expect, actual) {
t.Fatal(`expect is not equal to actual`) t.Fatal("expect is not equal to actual")
} }
} }
case bool: case bool:
if actual, err = v.Bool(); err == nil { if actual, err = v.Bool(); err == nil {
if !reflect.DeepEqual(test.expect, actual) { if !reflect.DeepEqual(test.expect, actual) {
t.Fatal(`expect is not equal to actual`) t.Fatal("expect is not equal to actual")
} }
} }
case float64: case float64:
if actual, err = v.Float(); err == nil { if actual, err = v.Float(); err == nil {
if !reflect.DeepEqual(test.expect, actual) { if !reflect.DeepEqual(test.expect, actual) {
t.Fatal(`expect is not equal to actual`) t.Fatal("expect is not equal to actual")
} }
} }
default: default:
@ -197,3 +198,31 @@ 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)
}
}
}

@ -11,6 +11,8 @@ import (
"github.com/imdario/mergo" "github.com/imdario/mergo"
"google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"github.com/go-kratos/kratos/v2/log"
) )
// Reader is config reader. // Reader is config reader.
@ -36,18 +38,18 @@ func newReader(opts options) Reader {
} }
func (r *reader) Merge(kvs ...*KeyValue) error { func (r *reader) Merge(kvs ...*KeyValue) error {
r.lock.Lock() merged, err := r.cloneMap()
merged, err := cloneMap(r.values)
r.lock.Unlock()
if err != nil { if err != nil {
return err return err
} }
for _, kv := range kvs { for _, kv := range kvs {
next := make(map[string]interface{}) next := make(map[string]interface{})
if err := r.opts.decoder(kv, next); err != nil { if err := r.opts.decoder(kv, next); err != nil {
log.Errorf("Failed to config decode error: %v key: %s value: %s", err, kv.Key, string(kv.Value))
return err return err
} }
if err := mergo.Map(&merged, convertMap(next), mergo.WithOverride); err != nil { if err := mergo.Map(&merged, convertMap(next), mergo.WithOverride); err != nil {
log.Errorf("Failed to config merge error: %v key: %s value: %s", err, kv.Key, string(kv.Value))
return err return err
} }
} }
@ -75,6 +77,12 @@ func (r *reader) Resolve() error {
return r.opts.resolver(r.values) 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) { func cloneMap(src map[string]interface{}) (map[string]interface{}, error) {
// https://gist.github.com/soroushjp/0ec92102641ddfc3ad5515ca76405f4d // https://gist.github.com/soroushjp/0ec92102641ddfc3ad5515ca76405f4d
var buf bytes.Buffer var buf bytes.Buffer
@ -86,12 +94,12 @@ func cloneMap(src map[string]interface{}) (map[string]interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var copy map[string]interface{} var clone map[string]interface{}
err = dec.Decode(&copy) err = dec.Decode(&clone)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return copy, nil return clone, nil
} }
func convertMap(src interface{}) interface{} { func convertMap(src interface{}) interface{} {

@ -29,7 +29,7 @@ func TestReader_Merge(t *testing.T) {
Format: "json", Format: "json",
}) })
if err == nil { if err == nil {
t.Fatal(`err is nil`) t.Fatal("err is nil")
} }
err = r.Merge(&KeyValue{ err = r.Merge(&KeyValue{
@ -38,15 +38,15 @@ func TestReader_Merge(t *testing.T) {
Format: "json", Format: "json",
}) })
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
vv, ok := r.Value("nice") vv, ok := r.Value("nice")
if !ok { if !ok {
t.Fatal(`ok is false`) t.Fatal("ok is false")
} }
vvv, err := vv.String() vvv, err := vv.String()
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
if vvv != "boat" { if vvv != "boat" {
t.Fatal(`vvv is not equal to "boat"`) t.Fatal(`vvv is not equal to "boat"`)
@ -58,18 +58,18 @@ func TestReader_Merge(t *testing.T) {
Format: "json", Format: "json",
}) })
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
vv, ok = r.Value("x") vv, ok = r.Value("x")
if !ok { if !ok {
t.Fatal(`ok is false`) t.Fatal("ok is false")
} }
vvx, err := vv.Int() vvx, err := vv.Int()
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
if int64(2) != vvx { if vvx != 2 {
t.Fatal(`vvx is not equal to 2`) t.Fatal("vvx is not equal to 2")
} }
} }
@ -118,63 +118,57 @@ a:
r := newReader(opts) r := newReader(opts)
err := r.Merge(&test.kv) err := r.Merge(&test.kv)
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
vv, ok := r.Value("a.b.X") vv, ok := r.Value("a.b.X")
if !ok { if !ok {
t.Fatal(`ok is false`) t.Fatal("ok is false")
} }
vvv, err := vv.Int() vvv, err := vv.Int()
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
if int64(1) != vvv { 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") vv, ok = r.Value("a.b.Y")
if !ok { if !ok {
t.Fatal(`ok is false`) t.Fatal("ok is false")
} }
vvy, err := vv.String() vvy, err := vv.String()
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
if vvy != "lol" { if vvy != "lol" {
t.Fatal(`vvy is not equal to "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") vv, ok = r.Value("a.b.z")
if !ok { if !ok {
t.Fatal(`ok is false`) t.Fatal("ok is false")
} }
vvz, err := vv.Bool() vvz, err := vv.Bool()
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
if !vvz { if !vvz {
t.Fatal(`vvz is not equal to true`) t.Fatal("vvz is not equal to true")
} }
_, ok = r.Value("aasasdg=234l.asdfk,") _, ok = r.Value("aasasdg=234l.asdfk,")
if ok { if ok {
t.Fatal(`ok is true`) t.Fatal("ok is true")
} }
_, ok = r.Value("aas......asdg=234l.asdfk,") _, ok = r.Value("aas......asdg=234l.asdfk,")
if ok { if ok {
t.Fatal(`ok is true`) t.Fatal("ok is true")
} }
_, ok = r.Value("a.b.Y.") _, ok = r.Value("a.b.Y.")
if ok { if ok {
t.Fatal(`ok is true`) t.Fatal("ok is true")
} }
}) })
} }
@ -198,13 +192,128 @@ func TestReader_Source(t *testing.T) {
Format: "json", Format: "json",
}) })
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
b, err := r.Source() b, err := r.Source()
if err != nil { if err != nil {
t.Fatal(`err is not nil`) t.Fatal(err)
} }
if !reflect.DeepEqual([]byte(`{"a":{"b":{"X":1}}}`), b) { if !reflect.DeepEqual([]byte(`{"a":{"b":{"X":1}}}`), b) {
t.Fatal("[]byte(`{\"a\":{\"b\":{\"X\":1}}}`) is not equal to 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 package config
import ( import (
stdjson "encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
@ -10,7 +10,7 @@ import (
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"github.com/go-kratos/kratos/v2/encoding/json" kratosjson "github.com/go-kratos/kratos/v2/encoding/json"
) )
var ( var (
@ -36,6 +36,10 @@ type atomicValue struct {
atomic.Value 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) { func (v *atomicValue) Bool() (bool, error) {
switch val := v.Load().(type) { switch val := v.Load().(type) {
case bool: case bool:
@ -43,7 +47,7 @@ func (v *atomicValue) Bool() (bool, error) {
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, string: case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, string:
return strconv.ParseBool(fmt.Sprint(val)) return strconv.ParseBool(fmt.Sprint(val))
} }
return false, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load())) return false, v.typeAssertError()
} }
func (v *atomicValue) Int() (int64, error) { func (v *atomicValue) Int() (int64, error) {
@ -73,35 +77,37 @@ func (v *atomicValue) Int() (int64, error) {
case float64: case float64:
return int64(val), nil return int64(val), nil
case string: case string:
return strconv.ParseInt(val, 10, 64) //nolint:gomnd return strconv.ParseInt(val, 10, 64)
} }
return 0, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load())) return 0, v.typeAssertError()
} }
func (v *atomicValue) Slice() ([]Value, error) { func (v *atomicValue) Slice() ([]Value, error) {
if vals, ok := v.Load().([]interface{}); ok { vals, ok := v.Load().([]interface{})
var slices []Value if !ok {
for _, val := range vals { return nil, v.typeAssertError()
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())) slices := make([]Value, 0, len(vals))
for _, val := range vals {
a := new(atomicValue)
a.Store(val)
slices = append(slices, a)
}
return slices, nil
} }
func (v *atomicValue) Map() (map[string]Value, error) { func (v *atomicValue) Map() (map[string]Value, error) {
if vals, ok := v.Load().(map[string]interface{}); ok { vals, ok := v.Load().(map[string]interface{})
m := make(map[string]Value) if !ok {
for key, val := range vals { return nil, v.typeAssertError()
a := &atomicValue{} }
a.Store(val) m := make(map[string]Value, len(vals))
m[key] = a for key, val := range vals {
} a := new(atomicValue)
return m, nil a.Store(val)
m[key] = a
} }
return nil, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load())) return m, nil
} }
func (v *atomicValue) Float() (float64, error) { func (v *atomicValue) Float() (float64, error) {
@ -131,9 +137,9 @@ func (v *atomicValue) Float() (float64, error) {
case float64: case float64:
return val, nil return val, nil
case string: case string:
return strconv.ParseFloat(val, 64) //nolint:gomnd return strconv.ParseFloat(val, 64)
} }
return 0.0, fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load())) return 0.0, v.typeAssertError()
} }
func (v *atomicValue) String() (string, error) { func (v *atomicValue) String() (string, error) {
@ -144,12 +150,10 @@ func (v *atomicValue) String() (string, error) {
return fmt.Sprint(val), nil return fmt.Sprint(val), nil
case []byte: case []byte:
return string(val), nil return string(val), nil
default: case fmt.Stringer:
if s, ok := val.(fmt.Stringer); ok { return val.String(), nil
return s.String(), nil
}
} }
return "", fmt.Errorf("type assert to %v failed", reflect.TypeOf(v.Load())) return "", v.typeAssertError()
} }
func (v *atomicValue) Duration() (time.Duration, error) { func (v *atomicValue) Duration() (time.Duration, error) {
@ -161,14 +165,14 @@ func (v *atomicValue) Duration() (time.Duration, error) {
} }
func (v *atomicValue) Scan(obj interface{}) error { func (v *atomicValue) Scan(obj interface{}) error {
data, err := stdjson.Marshal(v.Load()) data, err := json.Marshal(v.Load())
if err != nil { if err != nil {
return err return err
} }
if pb, ok := obj.(proto.Message); ok { if pb, ok := obj.(proto.Message); ok {
return json.UnmarshalOptions.Unmarshal(data, pb) return kratosjson.UnmarshalOptions.Unmarshal(data, pb)
} }
return stdjson.Unmarshal(data, obj) return json.Unmarshal(data, obj)
} }
type errValue struct { type errValue struct {

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

@ -55,9 +55,6 @@ func WithEnableBackup() Option
// specify apollo endpoint, such as http://localhost:8080 // specify apollo endpoint, such as http://localhost:8080
func WithEndpoint(endpoint string) Option func WithEndpoint(endpoint string) Option
// inject a logger to debug
func WithLogger(logger log.Logger) Option
// namespaces to load, comma to separate. // namespaces to load, comma to separate.
func WithNamespace(name string) Option func WithNamespace(name string) Option
@ -100,5 +97,4 @@ config := map[string]interface{}{
} }
} }
} }
_ = config
``` ```

@ -1,15 +1,16 @@
package apollo package apollo
import ( import (
"fmt"
"strings" "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/config"
"github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/encoding"
"github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/log"
"github.com/apolloconfig/agollo/v4"
apolloConfig "github.com/apolloconfig/agollo/v4/env/config"
) )
type apollo struct { type apollo struct {
@ -17,6 +18,15 @@ type apollo struct {
opt *options opt *options
} }
const (
yaml = "yaml"
yml = "yml"
json = "json"
properties = "properties"
)
var formats map[string]struct{}
// Option is apollo option // Option is apollo option
type Option func(*options) type Option func(*options)
@ -28,8 +38,7 @@ type options struct {
namespace string namespace string
isBackupConfig bool isBackupConfig bool
backupPath string backupPath string
originConfig bool
logger log.Logger
} }
// WithAppID with apollo config app id // WithAppID with apollo config app id
@ -88,24 +97,23 @@ func WithBackupPath(backupPath string) Option {
} }
} }
// WithLogger use custom logger to replace default logger. // WithOriginalConfig use the original configuration file without parse processing
func WithLogger(logger log.Logger) Option { func WithOriginalConfig() Option {
return func(o *options) { return func(o *options) {
if logger != nil { extension.AddFormatParser(constant.JSON, &jsonExtParser{})
o.logger = logger extension.AddFormatParser(constant.YAML, &yamlExtParser{})
} extension.AddFormatParser(constant.YML, &yamlExtParser{})
o.originConfig = true
} }
} }
func NewSource(opts ...Option) config.Source { func NewSource(opts ...Option) config.Source {
op := options{ op := options{}
logger: log.GetLogger(),
}
for _, o := range opts { for _, o := range opts {
o(&op) o(&op)
} }
client, err := agollo.StartWithConfig(func() (*apolloConfig.AppConfig, error) { client, err := agollo.StartWithConfig(func() (*apolloconfig.AppConfig, error) {
return &apolloConfig.AppConfig{ return &apolloconfig.AppConfig{
AppID: op.appid, AppID: op.appid,
Cluster: op.cluster, Cluster: op.cluster,
NamespaceName: op.namespace, NamespaceName: op.namespace,
@ -118,26 +126,100 @@ func NewSource(opts ...Option) config.Source {
if err != nil { if err != nil {
panic(err) panic(err)
} }
return &apollo{client: client, opt: &op} return &apollo{client: client, opt: &op}
} }
// genKey got the key of config.KeyValue pair. func format(ns string) string {
// eg: namespace.ext with subKey got namespace.subKey
func genKey(ns, sub string) string {
arr := strings.Split(ns, ".") arr := strings.Split(ns, ".")
if len(arr) < 1 { suffix := arr[len(arr)-1]
return sub if len(arr) <= 1 || suffix == properties {
return json
}
if _, ok := formats[suffix]; !ok {
// fallback
return json
} }
if len(arr) == 1 { return suffix
if ns == "" { }
return sub
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
} }
return ns + "." + sub 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)
} }
return kvs
}
return strings.Join(arr[:len(arr)-1], ".") + "." + sub 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
}
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
}
// 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
} }
// resolve convert kv pair into one map[string]interface{} by split key into different // resolve convert kv pair into one map[string]interface{} by split key into different
@ -167,66 +249,38 @@ func resolve(key string, value interface{}, target map[string]interface{}) {
// current exists, then check existing value type, if it's not map // current exists, then check existing value type, if it's not map
// that means duplicate keys, and at least one is not map instance. // that means duplicate keys, and at least one is not map instance.
if cursor, ok = v.(map[string]interface{}); !ok { if cursor, ok = v.(map[string]interface{}); !ok {
_ = log.GetLogger().Log(log.LevelWarn, log.Warnf("duplicate key: %v\n", strings.Join(keys[:i+1], "."))
"msg",
fmt.Sprintf("duplicate key: %v\n", strings.Join(keys[:i+1], ".")),
)
break break
} }
} }
} }
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, ".") arr := strings.Split(ns, ".")
if len(arr) <= 1 { if len(arr) == 1 {
return "json" if ns == "" {
} return 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 {
_ = e.opt.logger.Log(log.LevelWarn,
"msg",
fmt.Sprintf("apollo could not handle namespace %s: %v", ns, err),
)
continue
} }
kv = append(kv, &config.KeyValue{ return ns + "." + sub
Key: ns, }
Value: val,
Format: f, suffix := arr[len(arr)-1]
}) _, ok := formats[suffix]
if ok {
return strings.Join(arr[:len(arr)-1], ".") + "." + sub
} }
return kv return ns + "." + sub
} }
func (e *apollo) Load() (kv []*config.KeyValue, err error) { func init() {
return e.load(), nil formats = make(map[string]struct{})
}
func (e *apollo) Watch() (config.Watcher, error) { formats[yaml] = struct{}{}
w, err := newWatcher(e, e.opt.logger) formats[yml] = struct{}{}
if err != nil { formats[json] = struct{}{}
return nil, err formats[properties] = struct{}{}
}
return w, nil
} }

@ -1,7 +1,6 @@
package apollo package apollo
import ( import (
"reflect"
"testing" "testing"
) )
@ -16,44 +15,28 @@ func Test_genKey(t *testing.T) {
want string want string
}{ }{
{ {
name: "case 1", name: "blank namespace",
args: args{ args: args{
ns: "", ns: "",
sub: "has_no_ns", sub: "x.y",
}, },
want: "has_no_ns", want: "x.y",
}, },
{ {
name: "case 2", name: "properties namespace",
args: args{ args: args{
ns: "ns.ext", ns: "application",
sub: "sub", sub: "x.y",
}, },
want: "ns.sub", want: "application.x.y",
}, },
{ {
name: "case 3", name: "namespace with format",
args: args{ args: args{
ns: "", ns: "app.yaml",
sub: "", sub: "x.y",
},
want: "",
},
{
name: "case 4",
args: args{
ns: "ns.ext",
sub: "sub.sub2.sub3",
},
want: "ns.sub.sub2.sub3",
},
{
name: "case 5",
args: args{
ns: "ns.more.ext",
sub: "sub.sub2.sub3",
}, },
want: "ns.more.sub.sub2.sub3", want: "app.x.y",
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -66,124 +49,42 @@ func Test_genKey(t *testing.T) {
} }
func Test_format(t *testing.T) { func Test_format(t *testing.T) {
type args struct {
ns string
}
tests := []struct { tests := []struct {
name string name string
args args namespace string
want string want string
}{ }{
{ {
name: "case 0", name: "properties namespace",
args: args{ namespace: "application",
ns: "ns.yaml", want: "json",
},
want: "yaml",
}, },
{ {
name: "case 1", name: "properties namespace #1",
args: args{ namespace: "app.setting",
ns: "ns", want: "json",
},
want: "json",
}, },
{ {
name: "case 2", name: "namespace with format[yaml]",
args: args{ namespace: "app.yaml",
ns: "ns.more.json", want: "yaml",
},
want: "json",
}, },
{ {
name: "case 3", name: "namespace with format[yml]",
args: args{ namespace: "app.yml",
ns: "", want: "yml",
},
want: "json",
}, },
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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", name: "namespace with format[json]",
args: args{ namespace: "app.json",
key: "application.name", want: "json",
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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
resolve(tt.args.key, tt.args.value, tt.args.target) if got := format(tt.namespace); got != tt.want {
if !reflect.DeepEqual(tt.args.target, tt.want) { t.Errorf("format() = %v, want %v", got, 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,13 +3,10 @@ module github.com/go-kratos/kratos/contrib/config/apollo/v2
go 1.16 go 1.16
require ( require (
github.com/apolloconfig/agollo/v4 v4.2.0 github.com/apolloconfig/agollo/v4 v4.3.0
github.com/go-kratos/kratos/v2 v2.3.0 github.com/go-kratos/kratos/v2 v2.6.3
) )
require ( require github.com/spf13/viper v1.11.0 // indirect
github.com/spf13/viper v1.11.0 // indirect
gopkg.in/yaml.v3 v3.0.0
)
replace github.com/go-kratos/kratos/v2 => ../../../ replace github.com/go-kratos/kratos/v2 => ../../../

File diff suppressed because it is too large Load Diff

@ -1,41 +1,13 @@
package apollo package apollo
import (
"encoding/json"
"gopkg.in/yaml.v3"
"github.com/apolloconfig/agollo/v4/constant"
"github.com/apolloconfig/agollo/v4/extension"
)
type jsonExtParser struct{} type jsonExtParser struct{}
func (parser jsonExtParser) Parse(configContent interface{}) (map[string]interface{}, error) { func (parser jsonExtParser) Parse(configContent interface{}) (map[string]interface{}, error) {
v, ok := configContent.(string) return map[string]interface{}{"content": configContent}, nil
if !ok {
return nil, nil
}
out := make(map[string]interface{}, 4)
err := json.Unmarshal([]byte(v), &out)
return out, err
} }
type yamlExtParser struct{} type yamlExtParser struct{}
func (parser yamlExtParser) Parse(configContent interface{}) (out map[string]interface{}, err error) { func (parser yamlExtParser) Parse(configContent interface{}) (map[string]interface{}, error) {
v, ok := configContent.(string) return map[string]interface{}{"content": configContent}, nil
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,27 +2,45 @@ package apollo
import ( import (
"context" "context"
"fmt" "strings"
"github.com/apolloconfig/agollo/v4/storage"
"github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/config"
"github.com/go-kratos/kratos/v2/encoding" "github.com/go-kratos/kratos/v2/encoding"
"github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/log"
"github.com/apolloconfig/agollo/v4/storage"
) )
type watcher struct { type watcher struct {
out <-chan []*config.KeyValue out <-chan []*config.KeyValue
ctx context.Context
cancelFn func() cancelFn func()
} }
type customChangeListener struct { type customChangeListener struct {
in chan<- []*config.KeyValue in chan<- []*config.KeyValue
logger log.Logger apollo *apollo
} }
func (c *customChangeListener) onChange(namespace string, changes map[string]*storage.ConfigChange) []*config.KeyValue { func (c *customChangeListener) onChange(namespace string, changes map[string]*storage.ConfigChange) []*config.KeyValue {
kv := make([]*config.KeyValue, 0, 2) 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{}) next := make(map[string]interface{})
for key, change := range changes { for key, change := range changes {
@ -33,10 +51,7 @@ func (c *customChangeListener) onChange(namespace string, changes map[string]*st
codec := encoding.GetCodec(f) codec := encoding.GetCodec(f)
val, err := codec.Marshal(next) val, err := codec.Marshal(next)
if err != nil { if err != nil {
_ = c.logger.Log(log.LevelWarn, log.Warnf("apollo could not handle namespace %s: %v", namespace, err)
"msg",
fmt.Sprintf("apollo could not handle namespace %s: %v", namespace, err),
)
return nil return nil
} }
kv = append(kv, &config.KeyValue{ kv = append(kv, &config.KeyValue{
@ -57,39 +72,38 @@ func (c *customChangeListener) OnChange(changeEvent *storage.ChangeEvent) {
c.in <- change c.in <- change
} }
func (c *customChangeListener) OnNewestChange(changeEvent *storage.FullChangeEvent) {} func (c *customChangeListener) OnNewestChange(_ *storage.FullChangeEvent) {}
func newWatcher(a *apollo, logger log.Logger) (config.Watcher, error) {
if logger == nil {
logger = log.GetLogger()
}
func newWatcher(a *apollo) (config.Watcher, error) {
changeCh := make(chan []*config.KeyValue) changeCh := make(chan []*config.KeyValue)
listener := &customChangeListener{in: changeCh, logger: logger} listener := &customChangeListener{in: changeCh, apollo: a}
a.client.AddChangeListener(listener) a.client.AddChangeListener(listener)
ctx, cancel := context.WithCancel(context.Background())
return &watcher{ return &watcher{
out: changeCh, out: changeCh,
ctx: ctx,
cancelFn: func() { cancelFn: func() {
a.client.RemoveChangeListener(listener) a.client.RemoveChangeListener(listener)
close(changeCh) cancel()
}, },
}, nil }, nil
} }
// Next will be blocked until the Stop method is called // Next will be blocked until the Stop method is called
func (w *watcher) Next() ([]*config.KeyValue, error) { func (w *watcher) Next() ([]*config.KeyValue, error) {
kv, ok := <-w.out select {
if !ok { case kv := <-w.out:
return nil, context.Canceled return kv, nil
case <-w.ctx.Done():
return nil, w.ctx.Err()
} }
return kv, nil
} }
func (w *watcher) Stop() error { func (w *watcher) Stop() error {
if w.cancelFn != nil { if w.cancelFn != nil {
w.cancelFn() w.cancelFn()
} }
return nil return nil
} }

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

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

@ -3,8 +3,11 @@ package consul
import ( import (
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/go-kratos/kratos/v2/config"
) )
const testPath = "kratos/test/config" const testPath = "kratos/test/config"
@ -99,3 +102,158 @@ func TestExtToFormat(t *testing.T) {
t.Errorf("kvs[0].Format is %s", kvs[0].Format) 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,8 +3,13 @@ module github.com/go-kratos/kratos/contrib/config/consul/v2
go 1.15 go 1.15
require ( require (
github.com/go-kratos/kratos/v2 v2.3.0 github.com/armon/go-metrics v0.3.10 // indirect
github.com/hashicorp/consul/api v1.12.0 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
) )
replace github.com/go-kratos/kratos/v2 => ../../../ replace github.com/go-kratos/kratos/v2 => ../../../

File diff suppressed because it is too large Load Diff

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

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

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

@ -15,7 +15,8 @@ const testKey = "/kratos/test/config"
func TestConfig(t *testing.T) { func TestConfig(t *testing.T) {
client, err := clientv3.New(clientv3.Config{ client, err := clientv3.New(clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"}, 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -108,3 +109,61 @@ func TestExtToFormat(t *testing.T) {
t.Errorf("kvs[0].Format is %s", kvs[0].Format) 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 go 1.16
require ( require (
github.com/go-kratos/kratos/v2 v2.3.0 github.com/go-kratos/kratos/v2 v2.6.3
go.etcd.io/etcd/client/v3 v3.5.4 go.etcd.io/etcd/client/v3 v3.5.8
google.golang.org/grpc v1.46.2 google.golang.org/grpc v1.56.1
) )
replace github.com/go-kratos/kratos/v2 => ../../../ replace github.com/go-kratos/kratos/v2 => ../../../

File diff suppressed because it is too large Load Diff

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

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

@ -1,18 +1,44 @@
package kubernetes package kubernetes
import ( import (
"context"
"log" "log"
"path/filepath" "path/filepath"
"reflect"
"strings"
"testing" "testing"
"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"
"k8s.io/client-go/util/homedir" "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",
},
}
) )
func TestSource(t *testing.T) { func TestSource(t *testing.T) {
home := homedir.HomeDir() home := homedir.HomeDir()
s := NewSource( s := NewSource(
Namespace("mesh"), Namespace("default"),
LabelSelector(""), LabelSelector(""),
KubeConfig(filepath.Join(home, ".kube", "config")), KubeConfig(filepath.Join(home, ".kube", "config")),
) )
@ -40,3 +66,143 @@ func ExampleNewSource() {
log.Panic(err) 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 go 1.16
require ( require (
github.com/go-kratos/kratos/v2 v2.3.0 github.com/go-kratos/kratos/v2 v2.6.3
k8s.io/api v0.24.0 k8s.io/api v0.26.3
k8s.io/apimachinery v0.24.0 k8s.io/apimachinery v0.26.3
k8s.io/client-go v0.24.0 k8s.io/client-go v0.26.3
) )
replace github.com/go-kratos/kratos/v2 => ../../../ replace github.com/go-kratos/kratos/v2 => ../../../

File diff suppressed because it is too large Load Diff

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

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

Loading…
Cancel
Save