Repository: go-kratos/kratos
Pull request #1224 (2021-07-21 07:57:01)

support expand config keys
支持将类似aaa.bbb的key展开成map[aaa]map[bbb]interface{}


这样可以实现通过key来覆盖文件中的结构体value，比如：
spring:
  cloud:
    consul:
      discovery:
        catalogServicesWatch:
          enabled: false
          
会被spring.cloud.consul.discovery.catalogServicesWatch.enabled=true 覆盖


Patch:
diff --git a/cmd/kratos/internal/base/repo.go b/cmd/kratos/internal/base/repo.go
index c00ba667..07a06744 100644
--- a/cmd/kratos/internal/base/repo.go
+++ b/cmd/kratos/internal/base/repo.go
@@ -1,7 +1,6 @@
 package base
 
 import (
-	"bytes"
 	"context"
 	"os"
 	"os/exec"
@@ -51,15 +50,17 @@ func (r *Repo) Path() string {
 
 // Pull fetch the repository from remote url.
 func (r *Repo) Pull(ctx context.Context) error {
-	cmd := exec.Command("git", "pull")
+	cmd := exec.Command("git", "symbolic-ref", "HEAD")
 	cmd.Dir = r.Path()
-	var out bytes.Buffer
-	cmd.Stderr = &out
-	cmd.Stdout = os.Stdout
 	err := cmd.Run()
-	if strings.Contains(out.String(), "You are not currently on a branch.") {
+	if err != nil {
 		return nil
 	}
+	cmd = exec.Command("git", "pull")
+	cmd.Dir = r.Path()
+	cmd.Stderr = os.Stderr
+	cmd.Stdout = os.Stdout
+	err = cmd.Run()
 	return err
 }
 
diff --git a/config/config.go b/config/config.go
index c6f5677d..9c1fccb0 100644
--- a/config/config.go
+++ b/config/config.go
@@ -67,11 +67,15 @@ func (c *config) watch(w Watcher) {
 		kvs, err := w.Next()
 		if err != nil {
 			time.Sleep(time.Second)
-			c.log.Errorf("Failed to watch next config: %v", err)
+			c.log.Errorf("failed to watch next config: %v", err)
 			continue
 		}
 		if err := c.reader.Merge(kvs...); err != nil {
-			c.log.Errorf("Failed to merge next config: %v", err)
+			c.log.Errorf("failed to merge next config: %v", err)
+			continue
+		}
+		if err := c.reader.Resolve(); err != nil {
+			c.log.Errorf("failed to resolve next config: %v", err)
 			continue
 		}
 		c.cached.Range(func(key, value interface{}) bool {
@@ -95,16 +99,20 @@ func (c *config) Load() error {
 			return err
 		}
 		if err := c.reader.Merge(kvs...); err != nil {
-			c.log.Errorf("Failed to merge config source: %v", err)
+			c.log.Errorf("failed to merge config source: %v", err)
 			return err
 		}
 		w, err := src.Watch()
 		if err != nil {
-			c.log.Errorf("Failed to watch config source: %v", err)
+			c.log.Errorf("failed to watch config source: %v", err)
 			return err
 		}
 		go c.watch(w)
 	}
+	if err := c.reader.Resolve(); err != nil {
+		c.log.Errorf("failed to resolve config source: %v", err)
+		return err
+	}
 	return nil
 }
 
diff --git a/config/options.go b/config/options.go
index 6a340b99..3574e974 100644
--- a/config/options.go
+++ b/config/options.go
@@ -61,7 +61,17 @@ func WithLogger(l log.Logger) Option {
 // to target map[string]interface{} using src.Format codec.
 func defaultDecoder(src *KeyValue, target map[string]interface{}) error {
 	if src.Format == "" {
-		target[src.Key] = src.Value
+		// expand key "aaa.bbb" into map[aaa]map[bbb]interface{}
+		keys := strings.Split(src.Key, ".")
+		for i, k := range keys {
+			if i == len(keys)-1 {
+				target[k] = src.Value
+			} else {
+				sub := make(map[string]interface{})
+				target[k] = sub
+				target = sub
+			}
+		}
 		return nil
 	}
 	if codec := encoding.GetCodec(src.Format); codec != nil {
diff --git a/config/reader.go b/config/reader.go
index 833508b4..c18b91d3 100644
--- a/config/reader.go
+++ b/config/reader.go
@@ -17,6 +17,7 @@ type Reader interface {
 	Merge(...*KeyValue) error
 	Value(string) (Value, bool)
 	Source() ([]byte, error)
+	Resolve() error
 }
 
 type reader struct {
@@ -45,9 +46,6 @@ func (r *reader) Merge(kvs ...*KeyValue) error {
 			return err
 		}
 	}
-	if err := r.opts.resolver(merged); err != nil {
-		return err
-	}
 	r.values = merged
 	return nil
 }
@@ -60,6 +58,10 @@ func (r *reader) Source() ([]byte, error) {
 	return marshalJSON(convertMap(r.values))
 }
 
+func (r *reader) Resolve() error {
+	return r.opts.resolver(r.values)
+}
+
 func cloneMap(src map[string]interface{}) (map[string]interface{}, error) {
 	// https://gist.github.com/soroushjp/0ec92102641ddfc3ad5515ca76405f4d
 	var buf bytes.Buffer
@@ -99,6 +101,9 @@ func convertMap(src interface{}) interface{} {
 			dst[k] = convertMap(v)
 		}
 		return dst
+	case []byte:
+		// there will be no binary data in the config data
+		return string(m)
 	default:
 		return src
 	}


Test patch:
diff --git a/config/env/env_test.go b/config/env/env_test.go
index 49d8e181..db5ec6d5 100644
--- a/config/env/env_test.go
+++ b/config/env/env_test.go
@@ -58,8 +58,8 @@ func TestEnvWithPrefix(t *testing.T) {
 	}
 
 	c := config.New(config.WithSource(
-		NewSource(prefix1, prefix2),
 		file.NewSource(path),
+		NewSource(prefix1, prefix2),
 	))
 
 	if err := c.Load(); err != nil {
diff --git a/config/options_test.go b/config/options_test.go
new file mode 100644
index 00000000..c3769166
--- /dev/null
+++ b/config/options_test.go
@@ -0,0 +1,37 @@
+package config
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDefaultDecoder(t *testing.T) {
+	src := &KeyValue{
+		Key:    "service",
+		Value:  []byte("config"),
+		Format: "",
+	}
+	target := make(map[string]interface{}, 0)
+	err := defaultDecoder(src, target)
+	assert.Nil(t, err)
+	assert.Equal(t, map[string]interface{}{
+		"service": []byte("config"),
+	}, target)
+
+	src = &KeyValue{
+		Key:    "service.name.alias",
+		Value:  []byte("2233"),
+		Format: "",
+	}
+	target = make(map[string]interface{}, 0)
+	err = defaultDecoder(src, target)
+	assert.Nil(t, err)
+	assert.Equal(t, map[string]interface{}{
+		"service": map[string]interface{}{
+			"name": map[string]interface{}{
+				"alias": []byte("2233"),
+			},
+		},
+	}, target)
+}