Repository: go-kratos/kratos
Pull request #2207 (2022-07-18 03:49:28)

feat: support passing program args with run cmd
implementation of #2169 
<!--
🎉 Thanks for sending a pull request to Kratos! Here are some tips for you:

1. If this is your first time contributing to Kratos, please read our contribution guide: https://go-kratos.dev/en/docs/community/contribution/
2. Ensure you have added or ran the appropriate tests and lint for your PR, please use `make lint` and `make test` before filing your PR, use `make clean` to tidy your go mod.
3. If the PR is unfinished, you may need to mark it as a WIP(Work In Progress) PR or draft PR
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、如果请购单未完成，您可能需要将其标记为 WIP（在制品）PR 或 draft PR
4、请使用语义提交格式标题，如“<类型>[可选范围]：<说明>`，请参阅：https://go-kratos.dev/docs/community/contribution#type
5. 同时请注意，同类的工作请尽量在一个PR中提交，以减轻 review 者的工作负担，不要把一项工作拆分成很多个PR，除非它应该这样做。
-->

<!--
-->

#### Description (what this PR does / why we need it):
<!--
* The description should include the motivation for this PR or contrast this with previous behavior
-->


#### Which issue(s) this PR fixes (resolves / be part of):
<!--
* Automatically closes linked issue when PR is merged.
* If your PR is not fully resolved the issue, please use `part of #<issue number>` instead.

Usage: `fixes/resolves #<issue number>`, or `fixes/resolves (paste link of issue)`.
-->


#### Other special notes for the reviewers:
<!--
* Somethings that need extra attention for the reviewers
* Some additional notes, TODO list, etc.
-->


codecov-commenter: # [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2207?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
> Merging [#2207](https://codecov.io/gh/go-kratos/kratos/pull/2207?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (2e02d23) into [main](https://codecov.io/gh/go-kratos/kratos/commit/1ab3d8f0284019f4f2e565be1553cb0dfd1701cd?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (1ab3d8f) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main    #2207   +/-   ##
=======================================
  Coverage   85.68%   85.68%           
=======================================
  Files          86       86           
  Lines        3758     3758           
=======================================
  Hits         3220     3220           
  Misses        371      371           
  Partials      167      167           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2207?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2207?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Last update [1ab3d8f...2e02d23](https://codecov.io/gh/go-kratos/kratos/pull/2207?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).


Patch:
diff --git a/README_zh.md b/README_zh.md
index 7bf99bf0..230f52f0 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -38,18 +38,18 @@ Kratos 一套轻量级 Go 微服务框架，包含大量微服务相关功能及
 * 工具链：包含大量工具链，比如 cache 代码生成，lint 工具等等；
 
 ## Features
-* [APIs](https://github.com/go-kratos/examples/helloworld/helloworld) ：协议通信以 HTTP/gRPC 为基础，通过 Protobuf 进行定义；
-* [Errors](https://github.com/go-kratos/examples/errors/api) ：通过 Protobuf 的 Enum 作为错误码定义，以及工具生成判定接口；
-* [Metadata](https://github.com/go-kratos/examples/metadata) ：在协议通信 HTTP/gRPC 中，通过 Middleware 规范化服务元信息传递；
-* [Config](https://github.com/go-kratos/examples/config) ：支持多数据源方式，进行配置合并铺平，通过 Atomic 方式支持动态配置；
-* [Logger](https://github.com/go-kratos/examples/log) ：标准日志接口，可方便集成三方 log 库，并可通过 fluentd 收集日志；
-* [Metrics](https://github.com/go-kratos/examples/metrics) ：统一指标接口，可以实现各种指标系统，默认集成 Prometheus；
-* [Tracing](https://github.com/go-kratos/examples/traces) ：遵循 OpenTelemetry 规范定义，以实现微服务链路追踪；
-* [Encoding](encoding)：支持 Accept 和 Content-Type 进行自动选择内容编码；
-* [Transport](transport/transport.go) ：通用的 [HTTP](https://github.com/go-kratos/examples/http/middlewares) /[gRPC](https://github.com/go-kratos/examples/middleware/main.go) 传输层，实现统一的 [Middleware](middleware) 插件支持；
-* [Registry](https://github.com/go-kratos/examples/registry) ：实现统一注册中心接口，可插件化对接各种注册中心；
-* [Validation](https://github.com/go-kratos/examples/validate): 通过Protobuf统一定义校验规则，并同时适用于HTTP/gRPC服务.
-* [SwaggerAPI](https://github.com/go-kratos/swagger-api/blob/main/examples/helloworld/server/main.go): 通过集成第三方[Swagger插件](https://github.com/go-kratos/swagger-api) 能够自动生成Swagger API json并启动一个内置的Swagger UI服务.
+* [APIs](https://go-kratos.dev/docs/component/api) ：协议通信以 HTTP/gRPC 为基础，通过 Protobuf 进行定义；
+* [Errors](https://go-kratos.dev/docs/component/errors/) ：通过 Protobuf 的 Enum 作为错误码定义，以及工具生成判定接口；
+* [Metadata](https://go-kratos.dev/docs/component/metadata) ：在协议通信 HTTP/gRPC 中，通过 Middleware 规范化服务元信息传递；
+* [Config](https://go-kratos.dev/docs/component/config) ：支持多数据源方式，进行配置合并铺平，通过 Atomic 方式支持动态配置；
+* [Logger](https://go-kratos.dev/docs/component/log) ：标准日志接口，可方便集成三方 log 库，并可通过 fluentd 收集日志；
+* [Metrics](https://go-kratos.dev/docs/component/middleware/metrics) ：统一指标接口，可以实现各种指标系统，默认集成 Prometheus；
+* [Tracing](https://go-kratos.dev/docs/component/middleware/tracing) ：遵循 OpenTelemetry 规范定义，以实现微服务链路追踪；
+* [Encoding](https://go-kratos.dev/docs/component/encoding) ：支持 Accept 和 Content-Type 进行自动选择内容编码；
+* [Transport](https://go-kratos.dev/docs/component/transport/overview) ：通用的 [HTTP](https://go-kratos.dev/docs/component/transport/http) /[gRPC](https://go-kratos.dev/docs/component/transport/grpc) 传输层，实现统一的 [Middleware](https://go-kratos.dev/docs/component/middleware/overview) 插件支持；
+* [Registry](https://go-kratos.dev/docs/component/registry) ：实现统一注册中心接口，可插件化对接各种注册中心；
+* [Validation](https://go-kratos.dev/docs/component/middleware/validate): 通过Protobuf统一定义校验规则，并同时适用于HTTP/gRPC服务.
+* [SwaggerAPI](https://go-kratos.dev/docs/guide/openapi): 通过集成第三方[Swagger插件](https://github.com/go-kratos/swagger-api) 能够自动生成Swagger API json并启动一个内置的Swagger UI服务.
 
 ## Getting Started
 ### Required
diff --git a/cmd/kratos/internal/project/add.go b/cmd/kratos/internal/project/add.go
index 98661cd1..f4c1b88c 100644
--- a/cmd/kratos/internal/project/add.go
+++ b/cmd/kratos/internal/project/add.go
@@ -12,7 +12,7 @@ import (
 )
 
 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 {
diff --git a/cmd/kratos/internal/run/run.go b/cmd/kratos/internal/run/run.go
index a84d45ba..ac229bab 100644
--- a/cmd/kratos/internal/run/run.go
+++ b/cmd/kratos/internal/run/run.go
@@ -23,8 +23,9 @@ var CmdRun = &cobra.Command{
 // Run run project.
 func Run(cmd *cobra.Command, args []string) {
 	var dir string
-	if len(args) > 0 {
-		dir = args[0]
+	cmdArgs, programArgs := splitArgs(cmd, args)
+	if len(cmdArgs) > 0 {
+		dir = cmdArgs[0]
 	}
 	base, err := os.Getwd()
 	if err != nil {
@@ -63,7 +64,7 @@ func Run(cmd *cobra.Command, args []string) {
 			dir = cmdPath[dir]
 		}
 	}
-	fd := exec.Command("go", "run", ".")
+	fd := exec.Command("go", append([]string{"run", "."}, programArgs...)...)
 	fd.Stdout = os.Stdout
 	fd.Stderr = os.Stderr
 	fd.Dir = dir
@@ -73,6 +74,14 @@ func Run(cmd *cobra.Command, args []string) {
 	}
 }
 
+func splitArgs(cmd *cobra.Command, args []string) (cmdArgs, programArgs []string) {
+	dashAt := cmd.ArgsLenAtDash()
+	if dashAt >= 0 {
+		return args[:dashAt], args[dashAt:]
+	}
+	return args, []string{}
+}
+
 func findCMD(base string) (map[string]string, error) {
 	wd, err := os.Getwd()
 	if err != nil {
diff --git a/contrib/config/polaris/config.go b/contrib/config/polaris/config.go
index f5d52d18..c3c39ece 100644
--- a/contrib/config/polaris/config.go
+++ b/contrib/config/polaris/config.go
@@ -43,11 +43,11 @@ func WithFileName(fileName string) Option {
 }
 
 type source struct {
-	client  *polaris.ConfigAPI
+	client  polaris.ConfigAPI
 	options *options
 }
 
-func New(client *polaris.ConfigAPI, opts ...Option) (config.Source, error) {
+func New(client polaris.ConfigAPI, opts ...Option) (config.Source, error) {
 	options := &options{
 		namespace: "default",
 		fileGroup: "",
@@ -74,12 +74,7 @@ func New(client *polaris.ConfigAPI, opts ...Option) (config.Source, error) {
 
 // Load return the config values
 func (s *source) Load() ([]*config.KeyValue, error) {
-	configAPI, err := polaris.NewConfigAPI()
-	if err != nil {
-		return nil, err
-	}
-
-	configFile, err := configAPI.GetConfigFile(s.options.namespace, s.options.fileGroup, s.options.fileName)
+	configFile, err := s.client.GetConfigFile(s.options.namespace, s.options.fileGroup, s.options.fileName)
 	if err != nil {
 		fmt.Println("fail to get config.", err)
 		return nil, err
diff --git a/contrib/log/logrus/logrus.go b/contrib/log/logrus/logrus.go
index 748055e5..b08ba6e1 100644
--- a/contrib/log/logrus/logrus.go
+++ b/contrib/log/logrus/logrus.go
@@ -33,6 +33,8 @@ func (l *Logger) Log(level log.Level, keyvals ...interface{}) (err error) {
 		logrusLevel = logrus.WarnLevel
 	case log.LevelError:
 		logrusLevel = logrus.ErrorLevel
+	case log.LevelFatal:
+		logrusLevel = logrus.FatalLevel
 	default:
 		logrusLevel = logrus.DebugLevel
 	}
diff --git a/contrib/registry/zookeeper/register.go b/contrib/registry/zookeeper/register.go
index 2aedc198..a9de43c8 100644
--- a/contrib/registry/zookeeper/register.go
+++ b/contrib/registry/zookeeper/register.go
@@ -3,6 +3,7 @@ package zookeeper
 import (
 	"context"
 	"path"
+	"time"
 
 	"github.com/go-zookeeper/zk"
 	"golang.org/x/sync/singleflight"
@@ -77,6 +78,7 @@ func (r *Registry) Register(ctx context.Context, service *registry.ServiceInstan
 	if err = r.ensureName(servicePath, data, zk.FlagEphemeral); err != nil {
 		return err
 	}
+	go r.reRegister(servicePath, data)
 	return nil
 }
 
@@ -150,3 +152,21 @@ func (r *Registry) ensureName(path string, data []byte, flags int32) error {
 	}
 	return nil
 }
+
+// reRegister re-register data node info when bad connection recovered
+func (r *Registry) reRegister(path string, data []byte) {
+	sessionID := r.conn.SessionID()
+	ticker := time.NewTicker(time.Second)
+	defer ticker.Stop()
+	for range ticker.C {
+		cur := r.conn.SessionID()
+		// sessionID changed
+		if cur > 0 && sessionID != cur {
+			// re-ensureName
+			if err := r.ensureName(path, data, zk.FlagEphemeral); err != nil {
+				return
+			}
+			sessionID = cur
+		}
+	}
+}


Test patch:
diff --git a/contrib/config/polaris/config_test.go b/contrib/config/polaris/config_test.go
index 6dfaeb3f..fc864526 100644
--- a/contrib/config/polaris/config_test.go
+++ b/contrib/config/polaris/config_test.go
@@ -213,7 +213,7 @@ func TestConfig(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	config, err := New(&configAPI, WithNamespace(namespace), WithFileGroup(fileGroup), WithFileName(name))
+	config, err := New(configAPI, WithNamespace(namespace), WithFileGroup(fileGroup), WithFileName(name))
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -285,7 +285,7 @@ func TestExtToFormat(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	config, err := New(&configAPI, WithNamespace(namespace), WithFileGroup(fileGroup), WithFileName(name))
+	config, err := New(configAPI, WithNamespace(namespace), WithFileGroup(fileGroup), WithFileName(name))
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/contrib/log/logrus/logrus_test.go b/contrib/log/logrus/logrus_test.go
index 5a6e69ae..3dcd6e64 100644
--- a/contrib/log/logrus/logrus_test.go
+++ b/contrib/log/logrus/logrus_test.go
@@ -32,6 +32,13 @@ func TestLoggerLog(t *testing.T) {
 			kvs:       []interface{}{"case", "level unmatch", "msg", "1"},
 			want:      "",
 		},
+		"fatal level": {
+			level:     logrus.InfoLevel,
+			formatter: &logrus.JSONFormatter{},
+			logLevel:  log.LevelFatal,
+			kvs:       []interface{}{"case", "json format", "msg", "1"},
+			want:      `{"case":"json format","level":"fatal","msg":"1"`,
+		},
 		"no tags": {
 			level:     logrus.InfoLevel,
 			formatter: &logrus.JSONFormatter{},