diff --git a/bindings/alicloud/nacos/nacos.go b/bindings/alicloud/nacos/nacos.go new file mode 100644 index 0000000000..f8a0610f6a --- /dev/null +++ b/bindings/alicloud/nacos/nacos.go @@ -0,0 +1,397 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation and Dapr Contributors. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +package nacos + +import ( + "errors" + "fmt" + "net/url" + "strconv" + "strings" + "time" + + "github.com/dapr/components-contrib/bindings" + "github.com/dapr/kit/logger" + "github.com/nacos-group/nacos-sdk-go/clients" + "github.com/nacos-group/nacos-sdk-go/clients/config_client" + "github.com/nacos-group/nacos-sdk-go/common/constant" + "github.com/nacos-group/nacos-sdk-go/vo" +) + +const ( + defaultGroup = "DEFAULT_GROUP" + defaultTimeout = 10 * time.Second + metadataConfigID = "config-id" + metadataConfigGroup = "config-group" + metadataConfigOnchange = "config-onchange" +) + +// Config type +type configParam struct { + dataID string + group string +} + +// Nacos allows reading/writing to a Nacos server +type Nacos struct { + settings Settings + config configParam + watches []configParam + servers []constant.ServerConfig + logger logger.Logger + configClient config_client.IConfigClient + readHandler func(response *bindings.ReadResponse) ([]byte, error) +} + +// NewNacos returns a new Nacos instance +func NewNacos(logger logger.Logger) *Nacos { + return &Nacos{logger: logger} //nolint:exhaustivestruct +} + +// Init implements InputBinding/OutputBinding's Init method +func (n *Nacos) Init(metadata bindings.Metadata) error { + n.settings = Settings{ + Timeout: defaultTimeout, + } + err := n.settings.Decode(metadata.Properties) + if err != nil { + return fmt.Errorf("nacos config error: %w", err) + } + + if err = n.settings.Validate(); err != nil { + return fmt.Errorf("nacos config error: %w", err) + } + + if n.settings.Endpoint != "" { + n.logger.Infof("nacos server url: %s", n.settings.Endpoint) + } else if n.settings.NameServer != "" { + n.logger.Infof("nacos nameserver: %s", n.settings.NameServer) + } + + if n.settings.Config != "" { + n.config, err = convertConfig(n.settings.Config) + if err != nil { + return err + } + } + n.watches, err = convertConfigs(n.settings.Watches) + if err != nil { + return err + } + + n.servers, err = convertServers(n.settings.Endpoint) + if err != nil { + return err + } + + return n.createConfigClient() +} + +func (n *Nacos) createConfigClient() error { + nacosConfig := map[string]interface{}{} + nacosConfig["clientConfig"] = constant.ClientConfig{ //nolint:exhaustivestruct + TimeoutMs: uint64(n.settings.Timeout), + NamespaceId: n.settings.NamespaceID, + Endpoint: n.settings.NameServer, + RegionId: n.settings.RegionID, + AccessKey: n.settings.AccessKey, + SecretKey: n.settings.SecretKey, + OpenKMS: n.settings.AccessKey != "" && n.settings.SecretKey != "", + CacheDir: n.settings.CacheDir, + UpdateThreadNum: n.settings.UpdateThreadNum, + NotLoadCacheAtStart: n.settings.NotLoadCacheAtStart, + UpdateCacheWhenEmpty: n.settings.UpdateCacheWhenEmpty, + Username: n.settings.Username, + Password: n.settings.Password, + LogDir: n.settings.LogDir, + RotateTime: n.settings.RotateTime, + MaxAge: int64(n.settings.MaxAge), + LogLevel: n.settings.LogLevel, + } + + if len(n.servers) > 0 { + nacosConfig["serverConfigs"] = n.servers + } + + var err error + n.configClient, err = clients.CreateConfigClient(nacosConfig) + if err != nil { + return fmt.Errorf("nacos config error: create config client failed. %w ", err) + } + + return nil +} + +// Read implements InputBinding's Read method +func (n *Nacos) Read(handler func(*bindings.ReadResponse) ([]byte, error)) error { + n.readHandler = handler + + for _, watch := range n.watches { + go n.startListen(watch) + } + + return nil +} + +// Close implements cancel all listeners, see https://github.com/dapr/components-contrib/issues/779 +func (n *Nacos) Close() error { + n.cancelListener() + + return nil +} + +// Invoke implements OutputBinding's Invoke method +func (n *Nacos) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) { + switch req.Operation { + case bindings.CreateOperation: + return n.publish(req) + case bindings.GetOperation: + return n.fetch(req) + case bindings.DeleteOperation, bindings.ListOperation: + return nil, fmt.Errorf("nacos error: unsupported operation %s", req.Operation) + default: + return nil, fmt.Errorf("nacos error: unsupported operation %s", req.Operation) + } +} + +// Operations implements OutputBinding's Operations method +func (n *Nacos) Operations() []bindings.OperationKind { + return []bindings.OperationKind{bindings.CreateOperation, bindings.GetOperation} +} + +func (n *Nacos) startListen(config configParam) { + n.fetchAndNotify(config) + n.addListener(config) +} + +func (n *Nacos) fetchAndNotify(config configParam) { + content, err := n.configClient.GetConfig(vo.ConfigParam{ + DataId: config.dataID, + Group: config.group, + Content: "", + DatumId: "", + OnChange: nil, + }) + if err != nil { + n.logger.Warnf("failed to receive nacos config %s:%s, error: %v", config.dataID, config.group, err) + } else { + n.notifyApp(config.group, config.dataID, content) + } +} + +func (n *Nacos) addListener(config configParam) { + err := n.configClient.ListenConfig(vo.ConfigParam{ + DataId: config.dataID, + Group: config.group, + Content: "", + DatumId: "", + OnChange: n.listener, + }) + if err != nil { + n.logger.Warnf("failed to add nacos listener for %s:%s, error: %v", config.dataID, config.group, err) + } +} + +func (n *Nacos) addListener4InputBinding(config configParam) { + if n.addToWatches(config) { + go n.addListener(config) + } +} + +func (n *Nacos) publish(req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) { + nacosConfigParam, err := n.findConfig(req.Metadata) + if err != nil { + return nil, err + } + + if _, err := n.configClient.PublishConfig(vo.ConfigParam{ + DataId: nacosConfigParam.dataID, + Group: nacosConfigParam.group, + Content: string(req.Data), + DatumId: "", + OnChange: nil, + }); err != nil { + return nil, fmt.Errorf("publish failed. %w", err) + } + + return nil, nil +} + +func (n *Nacos) fetch(req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) { + nacosConfigParam, err := n.findConfig(req.Metadata) + if err != nil { + return nil, err + } + + rst, err := n.configClient.GetConfig(vo.ConfigParam{ + DataId: nacosConfigParam.dataID, + Group: nacosConfigParam.group, + Content: "", + DatumId: "", + OnChange: nil, + }) + if err != nil { + return nil, fmt.Errorf("fetch failed. err:%w", err) + } + + if onchange := req.Metadata[metadataConfigOnchange]; strings.EqualFold(onchange, "true") { + n.addListener4InputBinding(*nacosConfigParam) + } + + return &bindings.InvokeResponse{Data: []byte(rst), Metadata: map[string]string{}}, nil +} + +func (n *Nacos) addToWatches(c configParam) bool { + if n.watches != nil { + for _, watch := range n.watches { + if c.dataID == watch.dataID && c.group == watch.group { + return false + } + } + } + n.watches = append(n.watches, c) + + return true +} + +func (n *Nacos) findConfig(md map[string]string) (*configParam, error) { + nacosConfigParam := n.config + if _, ok := md[metadataConfigID]; ok { + nacosConfigParam = configParam{ + dataID: md[metadataConfigID], + group: md[metadataConfigGroup], + } + } + + if nacosConfigParam.dataID == "" { + return nil, fmt.Errorf("nacos config error: invalid metadata, no dataID found: %v", md) + } + if nacosConfigParam.group == "" { + nacosConfigParam.group = defaultGroup + } + + return &nacosConfigParam, nil +} + +func (n *Nacos) listener(_, group, dataID, data string) { + n.notifyApp(group, dataID, data) +} + +func (n *Nacos) cancelListener() { + for _, configParam := range n.watches { + if err := n.configClient.CancelListenConfig(vo.ConfigParam{ //nolint:exhaustivestruct + DataId: configParam.dataID, + Group: configParam.group, + }); err != nil { + n.logger.Warnf("nacos cancel listener failed err: %v", err) + } + } +} + +func (n *Nacos) notifyApp(group, dataID, content string) { + metadata := map[string]string{ + metadataConfigID: dataID, + metadataConfigGroup: group, + } + var err error + if n.readHandler != nil { + n.logger.Debugf("binding-nacos read content to app") + _, err = n.readHandler(&bindings.ReadResponse{Data: []byte(content), Metadata: metadata}) + } else { + err = errors.New("nacos error: the InputBinding.Read handler not init") + } + + if err != nil { + n.logger.Errorf("nacos config %s:%s failed to notify application, error: %v", dataID, group, err) + } +} + +func convertConfig(s string) (configParam, error) { + nacosConfigParam := configParam{dataID: "", group: ""} + pair := strings.Split(s, ":") + nacosConfigParam.dataID = strings.TrimSpace(pair[0]) + if len(pair) == 2 { + nacosConfigParam.group = strings.TrimSpace(pair[1]) + } + if nacosConfigParam.group == "" { + nacosConfigParam.group = defaultGroup + } + if nacosConfigParam.dataID == "" { + return nacosConfigParam, fmt.Errorf("nacos config error: invalid config keys, no config-id defined: %s", s) + } + + return nacosConfigParam, nil +} + +func convertConfigs(ss string) ([]configParam, error) { + configs := make([]configParam, 0) + if ss == "" { + return configs, nil + } + + for _, s := range strings.Split(ss, ",") { + var nacosConfigParam, err = convertConfig(s) + if err != nil { + return nil, err + } + configs = append(configs, nacosConfigParam) + } + + return configs, nil +} + +func convertServers(ss string) ([]constant.ServerConfig, error) { + serverConfigs := make([]constant.ServerConfig, 0) + if ss == "" { + return serverConfigs, nil + } + + array := strings.Split(ss, ",") + for _, s := range array { + cfg, err := parseServerURL(s) + if err != nil { + return serverConfigs, fmt.Errorf("parse url:%s error:%w", s, err) + } + serverConfigs = append(serverConfigs, *cfg) + } + + return serverConfigs, nil +} + +func parseServerURL(s string) (*constant.ServerConfig, error) { + if !strings.HasPrefix(s, "http") { + s = "http://" + s + } + u, err := url.Parse(s) + if err != nil { + return nil, fmt.Errorf("nacos config error: server url %s error: %w", s, err) + } + + port := uint64(80) + if u.Scheme == "" { + u.Scheme = "http" + } else if u.Scheme == "https" { + port = uint64(443) + } + + if u.Port() != "" { + port, err = strconv.ParseUint(u.Port(), 10, 64) + if err != nil { + return nil, fmt.Errorf("nacos config error: server port %s err: %w", u.Port(), err) + } + } + + if u.Path == "" || u.Path == "/" { + u.Path = "/nacos" + } + + return &constant.ServerConfig{ + ContextPath: u.Path, + IpAddr: u.Hostname(), + Port: port, + Scheme: u.Scheme, + }, nil +} diff --git a/bindings/alicloud/nacos/nacos_test.go b/bindings/alicloud/nacos/nacos_test.go new file mode 100644 index 0000000000..d6ded4a070 --- /dev/null +++ b/bindings/alicloud/nacos/nacos_test.go @@ -0,0 +1,81 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation and Dapr Contributors. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +package nacos + +import ( + "fmt" + "os" + "path" + "sync/atomic" + "testing" + "time" + + "github.com/dapr/components-contrib/bindings" + "github.com/dapr/kit/logger" + "github.com/stretchr/testify/require" +) + +func TestInputBindingRead(t *testing.T) { //nolint:paralleltest + m := bindings.Metadata{Name: "test", Properties: nil} + var err error + m.Properties, err = getNacosLocalCacheMetadata() + require.NoError(t, err) + n := NewNacos(logger.NewLogger("test")) + err = n.Init(m) + require.NoError(t, err) + var count int32 + ch := make(chan bool, 1) + + handler := func(in *bindings.ReadResponse) ([]byte, error) { + require.Equal(t, "hello", string(in.Data)) + atomic.AddInt32(&count, 1) + ch <- true + + return nil, nil + } + + go func() { + err = n.Read(handler) + require.NoError(t, err) + }() + + select { + case <-ch: + require.Equal(t, int32(1), atomic.LoadInt32(&count)) + case <-time.After(time.Second): + require.FailNow(t, "read timeout") + } +} + +func getNacosLocalCacheMetadata() (map[string]string, error) { + tmpDir := "/tmp/config" + dataID := "test" + group := "DEFAULT_GROUP" + + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return nil, fmt.Errorf("create dir failed. %w", err) + } + + cfgFile := path.Join(tmpDir, fmt.Sprintf("%s@@%s@@", dataID, group)) + file, err := os.OpenFile(cfgFile, os.O_RDWR|os.O_CREATE, os.ModePerm) + if err != nil || file == nil { + return nil, fmt.Errorf("open %s failed. %w", cfgFile, err) + } + + defer func() { + _ = file.Close() + }() + + if _, err = file.WriteString("hello"); err != nil { + return nil, fmt.Errorf("write file failed. %w", err) + } + + return map[string]string{ + "cacheDir": "/tmp", // default + "nameServer": "localhost:8080/fake", + "watches": fmt.Sprintf("%s:%s", dataID, group), + }, nil +} diff --git a/bindings/alicloud/nacos/settings.go b/bindings/alicloud/nacos/settings.go new file mode 100644 index 0000000000..f349c400ca --- /dev/null +++ b/bindings/alicloud/nacos/settings.go @@ -0,0 +1,56 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation and Dapr Contributors. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +// Nacos is an easy-to-use dynamic service discovery, configuration and service management platform +// +// See https://github.com/nacos-group/nacos-sdk-go/ + +package nacos + +import ( + "errors" + "fmt" + "time" + + "github.com/dapr/components-contrib/internal/config" +) + +type Settings struct { + NameServer string `mapstructure:"nameServer"` + Endpoint string `mapstructure:"endpoint"` + RegionID string `mapstructure:"region"` + NamespaceID string `mapstructure:"namespace"` + AccessKey string `mapstructure:"accessKey"` + SecretKey string `mapstructure:"secretKey"` + Timeout time.Duration `mapstructure:"timeout"` + CacheDir string `mapstructure:"cacheDir"` + UpdateThreadNum int `mapstructure:"updateThreadNum"` + NotLoadCacheAtStart bool `mapstructure:"notLoadCacheAtStart"` + UpdateCacheWhenEmpty bool `mapstructure:"updateCacheWhenEmpty"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + LogDir string `mapstructure:"logDir"` + RotateTime string `mapstructure:"rotateTime"` + MaxAge int `mapstructure:"maxAge"` + LogLevel string `mapstructure:"logLevel"` + Config string `mapstructure:"config"` + Watches string `mapstructure:"watches"` +} + +func (s *Settings) Decode(in interface{}) error { + return config.Decode(in, s) +} + +func (s *Settings) Validate() error { + if s.Timeout <= 0 { + return fmt.Errorf("invalid timeout %s", s.Timeout) + } + + if s.Endpoint == "" && s.NameServer == "" { + return errors.New("either endpoint or nameserver must be configured") + } + + return nil +} diff --git a/bindings/alicloud/nacos/settings_test.go b/bindings/alicloud/nacos/settings_test.go new file mode 100644 index 0000000000..3fac9b63ce --- /dev/null +++ b/bindings/alicloud/nacos/settings_test.go @@ -0,0 +1,31 @@ +package nacos_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/dapr/components-contrib/bindings/alicloud/nacos" +) + +func TestParseMetadata(t *testing.T) { //nolint:paralleltest + props := map[string]string{ + "endpoint": "a", + "region": "b", + "namespace": "c", + "accessKey": "d", + "secretKey": "e", + "updateThreadNum": "3", + } + + var settings nacos.Settings + err := settings.Decode(props) + require.NoError(t, err) + assert.Equal(t, "a", settings.Endpoint) + assert.Equal(t, "b", settings.RegionID) + assert.Equal(t, "c", settings.NamespaceID) + assert.Equal(t, "d", settings.AccessKey) + assert.Equal(t, "e", settings.SecretKey) + assert.Equal(t, 3, settings.UpdateThreadNum) +} diff --git a/go.mod b/go.mod index b61afdb894..4c2b18cc71 100644 --- a/go.mod +++ b/go.mod @@ -19,10 +19,12 @@ require ( github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/Shopify/sarama v1.23.1 + github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect github.com/a8m/documentdb v1.2.1-0.20190920062420-efdd52fe0905 github.com/aerospike/aerospike-client-go v4.5.0+incompatible github.com/agrea/ptr v0.0.0-20180711073057-77a518d99b7b github.com/ajg/form v1.5.1 // indirect + github.com/alibaba/sentinel-golang v1.0.2 github.com/alicebob/miniredis/v2 v2.13.3 github.com/aliyun/aliyun-oss-go-sdk v2.0.7+incompatible github.com/andybalholm/brotli v1.0.1 // indirect @@ -51,6 +53,7 @@ require ( github.com/gavv/httpexpect v2.0.0+incompatible // indirect github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v0.3.0 // indirect + github.com/go-ole/go-ole v1.2.5 // indirect github.com/go-redis/redis/v8 v8.8.0 github.com/go-sql-driver/mysql v1.5.0 github.com/gocql/gocql v0.0.0-20191018090344-07ace3bab0f8 @@ -77,6 +80,7 @@ require ( github.com/microcosm-cc/bluemonday v1.0.7 // indirect github.com/mitchellh/mapstructure v1.3.3 github.com/moul/http2curl v1.0.0 // indirect + github.com/nacos-group/nacos-sdk-go v1.0.7 github.com/nats-io/nats-server/v2 v2.2.1 // indirect github.com/nats-io/nats-streaming-server v0.21.2 // indirect github.com/nats-io/nats.go v1.10.1-0.20210330225420-a0b1f60162f8 @@ -107,7 +111,6 @@ require ( github.com/zeebe-io/zeebe/clients/go v0.26.3 go.mongodb.org/mongo-driver v1.1.2 go.opencensus.io v0.22.5 // indirect - go.uber.org/atomic v1.6.0 // indirect goji.io v2.0.2+incompatible // indirect golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c diff --git a/go.sum b/go.sum index 0ac823aa6a..e45c64e212 100644 --- a/go.sum +++ b/go.sum @@ -128,6 +128,8 @@ github.com/Shopify/sarama v1.23.1/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sE github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/a8m/documentdb v1.2.1-0.20190920062420-efdd52fe0905 h1:lrOYmNobGcyWEjvMIMJERJx1Y4ttPFobY7RHAD+6e10= github.com/a8m/documentdb v1.2.1-0.20190920062420-efdd52fe0905/go.mod h1:4Z0mpi7fkyqjxUdGiNMO3vagyiUoiwLncaIX6AsW5z0= github.com/aerospike/aerospike-client-go v4.5.0+incompatible h1:6ALev/Ge4jW5avSLoqgvPYTh+FLeeDD9xDhzoMCNgOo= @@ -142,10 +144,14 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alibaba/sentinel-golang v1.0.2 h1:Acopq74hOtZN4MV1v811MQ6QcqPFLDSczTrRXv9zpIg= +github.com/alibaba/sentinel-golang v1.0.2/go.mod h1:QsB99f/z35D2AiMrAWwgWE85kDTkBUIkcmPrRt+61NI= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.13.3 h1:kohgdtN58KW/r9ZDVmMJE3MrfbumwsDQStd0LPAGmmw= github.com/alicebob/miniredis/v2 v2.13.3/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.18 h1:zOVTBdCKFd9JbCKz9/nt+FovbjPFmb7mUnp8nH9fQBA= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod h1:v8ESoHo4SyHmuB4b1tJqDHxfTGEciD+yhvOU/5s1Rfk= github.com/aliyun/aliyun-oss-go-sdk v2.0.7+incompatible h1:HXvOJsZw8JT/ldxjX74Aq4H2IY4ojV/mXMDPWFitpv8= github.com/aliyun/aliyun-oss-go-sdk v2.0.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/aliyunmq/mq-http-go-sdk v1.0.3 h1:/uhH7DUoaw9XTtsPgDp7zdPUyG5FBKj2GmJJph9z+6o= @@ -197,6 +203,8 @@ github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -313,6 +321,8 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/fasthttp-contrib/sessions v0.0.0-20160905201309-74f6ac73d5d5 h1:M4CVMQ5ueVmGZAtkW2bsO+ftesCYpfxl27JTqtzKBzE= github.com/fasthttp-contrib/sessions v0.0.0-20160905201309-74f6ac73d5d5/go.mod h1:MQXNGeXkpojWTxbN7vXoE3f7EmlA11MlJbsrJpVBINA= +github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= +github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= @@ -339,6 +349,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -352,6 +364,8 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.3.0 h1:q4c+kbcR0d5rSurhBR8dIgieOaYpXtsdTYfx22Cu6rs= github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= @@ -401,6 +415,7 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -613,10 +628,13 @@ github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= +github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= @@ -671,6 +689,12 @@ github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo= +github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE= +github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4= +github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f/go.mod h1:UGmTpUd3rjbtfIpwAPrcfmGf/Z1HS95TATB+m57TPB8= +github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 h1:Bvq8AziQ5jFF4BHGAEDSqwPW1NJS3XshxbRCxtjFAZc= +github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPpsiPUEh0zFL1Snz4crhMlBe60PYxRHr5oFF3rRYg0= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -740,6 +764,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nacos-group/nacos-sdk-go v1.0.7 h1:Am1tJFe7GUTNCREKsZ5ok0H2OspHDRmRcsxn7DiSwhA= +github.com/nacos-group/nacos-sdk-go v1.0.7/go.mod h1:hlAPn3UdzlxIlSILAyOXKxjFSvDJ9oLzTJ9hLAK1KzA= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/jwt v0.3.3-0.20200519195258-f2bf5ce574c7/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4w9M= @@ -922,6 +948,8 @@ github.com/sendgrid/sendgrid-go v3.5.0+incompatible h1:kosbgHyNVYVaqECDYvFVLVD9n github.com/sendgrid/sendgrid-go v3.5.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto= +github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= @@ -937,6 +965,7 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -975,6 +1004,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto= +github.com/tebeka/strftime v0.1.3/go.mod h1:7wJm3dZlpr4l/oVK0t1HYIc4rMzQ2XJlOMIUJUJH6XQ= github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E= github.com/tidwall/gjson v1.2.1 h1:j0efZLrZUvNerEf6xqoi0NjWMK5YlLrR7Guo/dxY174= github.com/tidwall/gjson v1.2.1/go.mod h1:c/nTNbUr0E0OrXEhq1pwa8iEgc2DOt4ZZqAt1HtCkPA= @@ -985,6 +1016,8 @@ github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8= github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3 h1:kF/7m/ZU+0D4Jj5eZ41Zm3IH/J8OElK1Qtd7tVKAwLk= +github.com/toolkits/concurrent v0.0.0-20150624120057-a4371d70e3e3/go.mod h1:QDlpd3qS71vYtakd2hmdpqhJ9nwv6mD6A30bQ1BPBFE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -1067,11 +1100,16 @@ go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.11.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= goji.io v2.0.2+incompatible h1:uIssv/elbKRLznFUy3Xj4+2Mz/qKhek/9aZQDUMae7c= goji.io v2.0.2+incompatible/go.mod h1:sbqFwrtqZACxLBTQcdgVjFh54yGVCvwq8+w49MVMMIk= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1225,6 +1263,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1475,6 +1514,8 @@ gopkg.in/gorethink/gorethink.v4 v4.1.0/go.mod h1:M7JgwrUAmshJ3iUbEK0Pt049MPyPK+C gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= @@ -1520,6 +1561,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.20.0 h1:WwrYoZNM1W1aQEbyl8HNG+oWGzLpZQBlcerS9BQw9yI= k8s.io/api v0.20.0/go.mod h1:HyLC5l5eoS/ygQYl1BXBgFzWNlkHiAuyNAbevIn+FKg= diff --git a/middleware/http/sentinel/logger.go b/middleware/http/sentinel/logger.go new file mode 100644 index 0000000000..3e01356622 --- /dev/null +++ b/middleware/http/sentinel/logger.go @@ -0,0 +1,46 @@ +package sentinel + +import ( + "github.com/alibaba/sentinel-golang/logging" + "github.com/dapr/kit/logger" +) + +type loggerAdaptor struct { + logger logger.Logger +} + +func (l *loggerAdaptor) Debug(msg string, keysAndValues ...interface{}) { + s := logging.AssembleMsg(logging.GlobalCallerDepth, "DEBUG", msg, nil, keysAndValues...) + l.logger.Debug(s) +} + +func (l *loggerAdaptor) DebugEnabled() bool { + return true +} + +func (l *loggerAdaptor) Info(msg string, keysAndValues ...interface{}) { + s := logging.AssembleMsg(logging.GlobalCallerDepth, "INFO", msg, nil, keysAndValues...) + l.logger.Info(s) +} + +func (l *loggerAdaptor) InfoEnabled() bool { + return true +} + +func (l *loggerAdaptor) Warn(msg string, keysAndValues ...interface{}) { + s := logging.AssembleMsg(logging.GlobalCallerDepth, "WARNING", msg, nil, keysAndValues...) + l.logger.Info(s) +} + +func (l *loggerAdaptor) WarnEnabled() bool { + return true +} + +func (l *loggerAdaptor) Error(err error, msg string, keysAndValues ...interface{}) { + s := logging.AssembleMsg(logging.GlobalCallerDepth, "ERROR", msg, nil, keysAndValues...) + l.logger.Info(s) +} + +func (l *loggerAdaptor) ErrorEnabled() bool { + return true +} diff --git a/middleware/http/sentinel/middleware.go b/middleware/http/sentinel/middleware.go new file mode 100644 index 0000000000..98206fdc5a --- /dev/null +++ b/middleware/http/sentinel/middleware.go @@ -0,0 +1,165 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation and Dapr Contributors. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +package sentinel + +import ( + "encoding/json" + "fmt" + + sentinel "github.com/alibaba/sentinel-golang/api" + "github.com/alibaba/sentinel-golang/core/base" + "github.com/alibaba/sentinel-golang/core/config" + "github.com/dapr/components-contrib/middleware" + "github.com/dapr/kit/logger" + "github.com/pkg/errors" + "github.com/valyala/fasthttp" +) + +type middlewareMetadata struct { + AppName string `json:"appName"` + // LogConfig + LogDir string `json:"logDir"` + // Rules + FlowRules string `yaml:"flowRules"` + CircuitBreakerRules string `yaml:"circuitBreakerRules"` + HotSpotParamRules string `yaml:"hotSpotParamRules"` + IsolationRules string `yaml:"isolationRules"` + SystemRules string `yaml:"systemRules"` +} + +// NewMiddleware returns a new sentinel middleware +func NewMiddleware(logger logger.Logger) *Middleware { + return &Middleware{logger: logger} +} + +// Middleware is an sentinel middleware +type Middleware struct { + logger logger.Logger +} + +// GetHandler returns the HTTP handler provided by sentinel middleware +func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.RequestHandler) fasthttp.RequestHandler, error) { + var ( + meta *middlewareMetadata + err error + ) + + meta, err = getNativeMetadata(metadata) + if err != nil { + return nil, errors.Wrap(err, "error to parse sentinel metadata") + } + + conf := m.newSentinelConfig(meta) + err = sentinel.InitWithConfig(conf) + if err != nil { + return nil, errors.Wrapf(err, "error to init sentinel with config: %s", conf) + } + + err = m.loadSentinelRules(meta) + if err != nil { + return nil, err + } + + return func(h fasthttp.RequestHandler) fasthttp.RequestHandler { + return func(ctx *fasthttp.RequestCtx) { + resourceName := string(ctx.Method()) + ":" + string(ctx.Path()) + entry, err := sentinel.Entry( + resourceName, + sentinel.WithResourceType(base.ResTypeWeb), + sentinel.WithTrafficType(base.Inbound), + ) + + if err != nil { + ctx.Error(fasthttp.StatusMessage(fasthttp.StatusTooManyRequests), fasthttp.StatusTooManyRequests) + + return + } + + defer entry.Exit() + h(ctx) + } + }, nil +} + +func (m *Middleware) loadSentinelRules(meta *middlewareMetadata) error { + if meta.FlowRules != "" { + err := loadRules(meta.FlowRules, newFlowRuleDataSource) + if err != nil { + msg := fmt.Sprintf("fail to load sentinel flow rules: %s", meta.FlowRules) + + return errors.Wrap(err, msg) + } + } + + if meta.IsolationRules != "" { + err := loadRules(meta.IsolationRules, newIsolationRuleDataSource) + if err != nil { + msg := fmt.Sprintf("fail to load sentinel isolation rules: %s", meta.IsolationRules) + + return errors.Wrap(err, msg) + } + } + + if meta.CircuitBreakerRules != "" { + err := loadRules(meta.CircuitBreakerRules, newCircuitBreakerRuleDataSource) + if err != nil { + msg := fmt.Sprintf("fail to load sentinel circuit breaker rules: %s", meta.CircuitBreakerRules) + + return errors.Wrap(err, msg) + } + } + + if meta.HotSpotParamRules != "" { + err := loadRules(meta.HotSpotParamRules, newHotSpotParamRuleDataSource) + if err != nil { + msg := fmt.Sprintf("fail to load sentinel hotspot param rules: %s", meta.HotSpotParamRules) + + return errors.Wrap(err, msg) + } + } + + if meta.SystemRules != "" { + err := loadRules(meta.SystemRules, newSystemRuleDataSource) + if err != nil { + msg := fmt.Sprintf("fail to load sentinel system rules: %s", meta.SystemRules) + + return errors.Wrap(err, msg) + } + } + + return nil +} + +func (m *Middleware) newSentinelConfig(metadata *middlewareMetadata) *config.Entity { + conf := config.NewDefaultConfig() + + if metadata.AppName != "" { + conf.Sentinel.App.Name = metadata.AppName + } + + if metadata.LogDir != "" { + conf.Sentinel.Log.Dir = metadata.LogDir + } + + conf.Sentinel.Log.Logger = &loggerAdaptor{m.logger} + + return conf +} + +func getNativeMetadata(metadata middleware.Metadata) (*middlewareMetadata, error) { + b, err := json.Marshal(metadata.Properties) + if err != nil { + return nil, err + } + + var md middlewareMetadata + err = json.Unmarshal(b, &md) + if err != nil { + return nil, err + } + + return &md, nil +} diff --git a/middleware/http/sentinel/middleware_test.go b/middleware/http/sentinel/middleware_test.go new file mode 100644 index 0000000000..a37d9ebf08 --- /dev/null +++ b/middleware/http/sentinel/middleware_test.go @@ -0,0 +1,123 @@ +package sentinel + +import ( + "testing" + + "github.com/dapr/components-contrib/middleware" + "github.com/dapr/kit/logger" + "github.com/stretchr/testify/assert" + "github.com/valyala/fasthttp" +) + +type counter struct { + count int32 +} + +func (c *counter) handle(ctx *fasthttp.RequestCtx) { + c.count++ +} + +func TestRequestHandlerWithFlowRules(t *testing.T) { + meta := middleware.Metadata{Properties: map[string]string{ + "appName": "test-app", + "flowRules": `[ + { + "resource": "GET:/v1.0/nodeapp/healthz", + "threshold": 10, + "tokenCalculateStrategy": 0, + "controlBehavior": 0 + } +]`, + }} + + log := logger.NewLogger("sentinel.test") + sentinel := NewMiddleware(log) + handler, err := sentinel.GetHandler(meta) + assert.Nil(t, err) + + var ctx fasthttp.RequestCtx + ctx.Request.SetHost("localhost:5001") + ctx.Request.SetRequestURI("/v1.0/nodeapp/healthz") + ctx.Request.Header.SetMethod("GET") + + counter := &counter{} + for i := 0; i < 100; i++ { + handler(counter.handle)(&ctx) + } + + assert.Equal(t, int32(10), counter.count) +} + +func TestLoadRules(t *testing.T) { + cases := []struct { + name string + meta middlewareMetadata + expectErr bool + }{ + { + name: "Invalid flow rules but return no error", + meta: middlewareMetadata{ + AppName: "nodeapp", + FlowRules: `[ + { + "resource": "GET:/v1.0/nodeapp/healthz", + "strategy": 1, + "statIntervalInMs": -1 + } +]`, + }, + expectErr: true, + }, + { + name: "Invalid circuit breaker rules and return error", + meta: middlewareMetadata{ + AppName: "nodeapp", + CircuitBreakerRules: `[ + { + "resource": "GET:/v1.0/nodeapp/healthz", + "strategy": 1, + "not-existing-property": -1 + } +]`, + }, + expectErr: false, + }, + { + name: "Invalid hotspot rules and return no error", + meta: middlewareMetadata{ + AppName: "nodeapp", + HotSpotParamRules: `[ + { + "resource": "GET:/v1.0/nodeapp/healthz", + "metricType": 1, + "not-existing-property": -1 + } +]`, + }, + expectErr: false, + }, + { + name: "Invalid system rules and return no error", + meta: middlewareMetadata{ + AppName: "nodeapp", + SystemRules: `[ + { + } +]`, + }, + expectErr: false, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + sentinel := NewMiddleware(nil) + err := sentinel.loadSentinelRules(&c.meta) + if c.expectErr { + assert.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} diff --git a/middleware/http/sentinel/rules.go b/middleware/http/sentinel/rules.go new file mode 100644 index 0000000000..1b70e8a592 --- /dev/null +++ b/middleware/http/sentinel/rules.go @@ -0,0 +1,83 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation and Dapr Contributors. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +package sentinel + +import ( + "github.com/alibaba/sentinel-golang/ext/datasource" + "github.com/pkg/errors" +) + +type propertyDataSource struct { + datasource.Base + rules string +} + +func loadRules(rules string, newDatasource func(rules string) (datasource.DataSource, error)) error { + if rules != "" { + ds, err := newDatasource(rules) + if err != nil { + return err + } + + err = ds.Initialize() + if err != nil { + return err + } + } + + return nil +} + +func newFlowRuleDataSource(rules string) (datasource.DataSource, error) { + return newDataSource(rules, datasource.NewFlowRulesHandler(datasource.FlowRuleJsonArrayParser)) +} + +func newCircuitBreakerRuleDataSource(rules string) (datasource.DataSource, error) { + return newDataSource(rules, datasource.NewCircuitBreakerRulesHandler(datasource.CircuitBreakerRuleJsonArrayParser)) +} + +func newHotSpotParamRuleDataSource(rules string) (datasource.DataSource, error) { + return newDataSource(rules, datasource.NewHotSpotParamRulesHandler(datasource.HotSpotParamRuleJsonArrayParser)) +} + +func newIsolationRuleDataSource(rules string) (datasource.DataSource, error) { + return newDataSource(rules, datasource.NewIsolationRulesHandler(datasource.IsolationRuleJsonArrayParser)) +} + +func newSystemRuleDataSource(rules string) (datasource.DataSource, error) { + return newDataSource(rules, datasource.NewSystemRulesHandler(datasource.SystemRuleJsonArrayParser)) +} + +func newDataSource(rules string, handlers ...datasource.PropertyHandler) (datasource.DataSource, error) { + ds := &propertyDataSource{ + rules: rules, + } + for _, h := range handlers { + ds.AddPropertyHandler(h) + } + + return ds, nil +} + +func (p propertyDataSource) ReadSource() ([]byte, error) { + return []byte(p.rules), nil +} + +func (p propertyDataSource) Initialize() error { + src, err := p.ReadSource() + if err != nil { + err = errors.Errorf("Fail to read source, err: %+v", err) + + return err + } + + return p.Handle(src) +} + +func (p propertyDataSource) Close() error { + // no op + return nil +} diff --git a/middleware/http/sentinel/rules_test.go b/middleware/http/sentinel/rules_test.go new file mode 100644 index 0000000000..fbb347e926 --- /dev/null +++ b/middleware/http/sentinel/rules_test.go @@ -0,0 +1,95 @@ +package sentinel + +import ( + "encoding/json" + "testing" + + "github.com/alibaba/sentinel-golang/core/circuitbreaker" + "github.com/alibaba/sentinel-golang/core/flow" + "github.com/alibaba/sentinel-golang/core/isolation" + "github.com/alibaba/sentinel-golang/core/system" + "github.com/stretchr/testify/assert" +) + +func TestFlowRules(t *testing.T) { + rules := []*flow.Rule{ + { + Resource: "some-test", + Threshold: 100, + TokenCalculateStrategy: flow.Direct, + ControlBehavior: flow.Reject, + }, + } + + b, _ := json.Marshal(rules) + t.Logf("%s", b) + err := loadRules(string(b), newFlowRuleDataSource) + assert.Nil(t, err) +} + +func TestCircuitBreakerRules(t *testing.T) { + rules := []*circuitbreaker.Rule{ + { + Resource: "abc", + Strategy: circuitbreaker.ErrorCount, + RetryTimeoutMs: 3000, + MinRequestAmount: 10, + StatIntervalMs: 5000, + Threshold: 50, + }, + } + + b, _ := json.Marshal(rules) + t.Logf("%s", b) + err := loadRules(string(b), newCircuitBreakerRuleDataSource) + assert.Nil(t, err) +} + +func TestHotspotParamRules(t *testing.T) { + rules := ` +[ + { + "resource": "abc", + "metricType": 1, + "controlBehavior": 0, + "paramIndex": 1, + "threshold": 50, + "burstCount": 0, + "durationInSec": 1 + } +] +` + err := loadRules(rules, newHotSpotParamRuleDataSource) + assert.Nil(t, err) +} + +func TestIsolationRules(t *testing.T) { + rules := []*isolation.Rule{ + { + Resource: "abc", + MetricType: isolation.Concurrency, + Threshold: 12, + }, + } + + b, _ := json.Marshal(rules) + t.Logf("%s", b) + err := loadRules(string(b), newIsolationRuleDataSource) + assert.Nil(t, err) +} + +func TestSystemRules(t *testing.T) { + rules := []*system.Rule{ + { + ID: "test-id", + MetricType: system.InboundQPS, + TriggerCount: 1000, + Strategy: system.BBR, + }, + } + + b, _ := json.Marshal(rules) + t.Logf("%s", b) + err := loadRules(string(b), newSystemRuleDataSource) + assert.Nil(t, err) +} diff --git a/secretstores/hashicorp/vault/vault.go b/secretstores/hashicorp/vault/vault.go index 2a1c8de1a7..2d65777cb5 100644 --- a/secretstores/hashicorp/vault/vault.go +++ b/secretstores/hashicorp/vault/vault.go @@ -32,6 +32,7 @@ const ( componentCaPem string = "caPem" componentSkipVerify string = "skipVerify" componentTLSServerName string = "tlsServerName" + componentVaultToken string = "vaultToken" componentVaultTokenMountPath string = "vaultTokenMountPath" componentVaultKVPrefix string = "vaultKVPrefix" defaultVaultKVPrefix string = "dapr" @@ -43,6 +44,7 @@ const ( type vaultSecretStore struct { client *http.Client vaultAddress string + vaultToken string vaultTokenMountPath string vaultKVPrefix string @@ -92,23 +94,19 @@ func (v *vaultSecretStore) Init(metadata secretstores.Metadata) error { v.vaultAddress = address - // Generate TLS config - tlsConf := metadataToTLSConfig(props) + v.vaultToken = props[componentVaultToken] + v.vaultTokenMountPath = props[componentVaultTokenMountPath] - client, err := v.createHTTPClient(tlsConf) - if err != nil { - return fmt.Errorf("couldn't create client using config: %s", err) + // Test that at least one of them are set if not return error + if v.vaultToken == "" && v.vaultTokenMountPath == "" { + return fmt.Errorf("token mount path and token not set") } - v.client = client - - tokenMountPath := props[componentVaultTokenMountPath] - if tokenMountPath == "" { - return fmt.Errorf("token mount path not set") + // Test that both are not set. If so return error + if v.vaultToken != "" && v.vaultTokenMountPath != "" { + return fmt.Errorf("token mount path and token both set") } - v.vaultTokenMountPath = tokenMountPath - vaultKVPrefix := props[componentVaultKVPrefix] if vaultKVPrefix == "" { vaultKVPrefix = defaultVaultKVPrefix @@ -116,6 +114,16 @@ func (v *vaultSecretStore) Init(metadata secretstores.Metadata) error { v.vaultKVPrefix = vaultKVPrefix + // Generate TLS config + tlsConf := metadataToTLSConfig(props) + + client, err := v.createHTTPClient(tlsConf) + if err != nil { + return fmt.Errorf("couldn't create client using config: %s", err) + } + + v.client = client + return nil } @@ -262,6 +270,10 @@ func (v *vaultSecretStore) BulkGetSecret(req secretstores.BulkGetSecretRequest) } func (v *vaultSecretStore) readVaultToken() (string, error) { + if v.vaultToken != "" { + return v.vaultToken, nil + } + data, err := ioutil.ReadFile(v.vaultTokenMountPath) if err != nil { return "", fmt.Errorf("couldn't read vault token: %s", err) diff --git a/secretstores/hashicorp/vault/vault_test.go b/secretstores/hashicorp/vault/vault_test.go index 95189fdd57..5d392c4f94 100644 --- a/secretstores/hashicorp/vault/vault_test.go +++ b/secretstores/hashicorp/vault/vault_test.go @@ -18,7 +18,9 @@ import ( const ( // base64 encoded certificate - certificate = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVakNDQWpvQ0NRRFlZdzdMeXN4VXRUQU5CZ2txaGtpRzl3MEJBUXNGQURCck1Rc3dDUVlEVlFRR0V3SkQKUVRFWk1CY0dBMVVFQ0F3UVFuSnBkR2x6YUNCRGIyeDFiV0pwWVRFU01CQUdBMVVFQnd3SlZtRnVZMjkxZG1WeQpNUk13RVFZRFZRUUtEQXB0YVhOb2NtRmpiM0p3TVJnd0ZnWURWUVFEREE5MllYVnNkSEJ5YjJwbFkzUXVhVzh3CkhoY05NVGt4TVRBeE1UQTBPREV5V2hjTk1qQXhNRE14TVRBME9ERXlXakJyTVFzd0NRWURWUVFHRXdKRFFURVoKTUJjR0ExVUVDQXdRUW5KcGRHbHphQ0JEYjJ4MWJXSnBZVEVTTUJBR0ExVUVCd3dKVm1GdVkyOTFkbVZ5TVJNdwpFUVlEVlFRS0RBcHRhWE5vY21GamIzSndNUmd3RmdZRFZRUUREQTkyWVhWc2RIQnliMnBsWTNRdWFXOHdnZ0VpCk1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQ3JtaitTTmtGUHEvK2FXUFV1MlpFamtSK3AKTm1PeEVNSnZZcGhHNkJvRFAySE9ZbGRzdk9FWkRkbTBpWFlmeFIwZm5rUmtTMWEzSlZiYmhINWJnTElKb0dxcwo5aWpzN2hyQ0Rrdk9uRWxpUEZuc05pQ2NWNDNxNkZYaFMvNFpoNGpOMnlyUkU2UmZiS1BEeUw0a282NkFhSld1CnVkTldKVWpzSFZBSWowZHlnTXFKYm0rT29iSzk5ckUxcDg5Z3RNUStJdzFkWnUvUFF4SjlYOStMeXdxZUxPckQKOWhpNWkxajNFUUp2RXQxSVUzclEwc2E0NU5zZkt4YzEwZjdhTjJuSDQzSnhnMVRiZXNPOWYrcWlyeDBHYmVSYQpyVmNaazNVaFc2cHZmam9XbDBEc0NwNTJwZDBQN05rUmhmak44b2RMN0h3bFVIc1NqemlSYytsTG5YREJBZ01CCkFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBSVdKdmRPZ01PUnQxWk53SENkNTNieTlkMlBkcW5tWHFZZ20KNDZHK2Fvb1dSeTJKMEMwS3ZOVGZGbEJFOUlydzNXUTVNMnpqY25qSUp5bzNLRUM5TDdPMnQ1WC9LTGVDck5ZVgpIc1d4cU5BTVBGY2VBa09HT0I1TThGVllkdjJTaVV2UDJjMEZQSzc2WFVzcVNkdnRsWGFkTk5ENzE3T0NTNm0yCnBIVjh1NWJNd1VmR2NCVFpEV2o4bjIzRVdHaXdnYkJkdDc3Z3h3YWc5NTROZkM2Ny9nSUc5ZlRrTTQ4aVJCUzEKc0NGYVBjMkFIT3hiMSs0ajVCMVY2Z29iZDZYWkFvbHdNaTNHUUtkbEM1NXZNeTNwK09WbDNNbEc3RWNTVUpMdApwZ2ZKaWw3L3dTWWhpUnhJU3hrYkk5cWhvNEwzZm5PZVB3clFVd2FzU1ZiL1lxbHZ2WHM9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" + certificate = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVakNDQWpvQ0NRRFlZdzdMeXN4VXRUQU5CZ2txaGtpRzl3MEJBUXNGQURCck1Rc3dDUVlEVlFRR0V3SkQKUVRFWk1CY0dBMVVFQ0F3UVFuSnBkR2x6YUNCRGIyeDFiV0pwWVRFU01CQUdBMVVFQnd3SlZtRnVZMjkxZG1WeQpNUk13RVFZRFZRUUtEQXB0YVhOb2NtRmpiM0p3TVJnd0ZnWURWUVFEREE5MllYVnNkSEJ5YjJwbFkzUXVhVzh3CkhoY05NVGt4TVRBeE1UQTBPREV5V2hjTk1qQXhNRE14TVRBME9ERXlXakJyTVFzd0NRWURWUVFHRXdKRFFURVoKTUJjR0ExVUVDQXdRUW5KcGRHbHphQ0JEYjJ4MWJXSnBZVEVTTUJBR0ExVUVCd3dKVm1GdVkyOTFkbVZ5TVJNdwpFUVlEVlFRS0RBcHRhWE5vY21GamIzSndNUmd3RmdZRFZRUUREQTkyWVhWc2RIQnliMnBsWTNRdWFXOHdnZ0VpCk1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQ3JtaitTTmtGUHEvK2FXUFV1MlpFamtSK3AKTm1PeEVNSnZZcGhHNkJvRFAySE9ZbGRzdk9FWkRkbTBpWFlmeFIwZm5rUmtTMWEzSlZiYmhINWJnTElKb0dxcwo5aWpzN2hyQ0Rrdk9uRWxpUEZuc05pQ2NWNDNxNkZYaFMvNFpoNGpOMnlyUkU2UmZiS1BEeUw0a282NkFhSld1CnVkTldKVWpzSFZBSWowZHlnTXFKYm0rT29iSzk5ckUxcDg5Z3RNUStJdzFkWnUvUFF4SjlYOStMeXdxZUxPckQKOWhpNWkxajNFUUp2RXQxSVUzclEwc2E0NU5zZkt4YzEwZjdhTjJuSDQzSnhnMVRiZXNPOWYrcWlyeDBHYmVSYQpyVmNaazNVaFc2cHZmam9XbDBEc0NwNTJwZDBQN05rUmhmak44b2RMN0h3bFVIc1NqemlSYytsTG5YREJBZ01CCkFBRXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnRUJBSVdKdmRPZ01PUnQxWk53SENkNTNieTlkMlBkcW5tWHFZZ20KNDZHK2Fvb1dSeTJKMEMwS3ZOVGZGbEJFOUlydzNXUTVNMnpqY25qSUp5bzNLRUM5TDdPMnQ1WC9LTGVDck5ZVgpIc1d4cU5BTVBGY2VBa09HT0I1TThGVllkdjJTaVV2UDJjMEZQSzc2WFVzcVNkdnRsWGFkTk5ENzE3T0NTNm0yCnBIVjh1NWJNd1VmR2NCVFpEV2o4bjIzRVdHaXdnYkJkdDc3Z3h3YWc5NTROZkM2Ny9nSUc5ZlRrTTQ4aVJCUzEKc0NGYVBjMkFIT3hiMSs0ajVCMVY2Z29iZDZYWkFvbHdNaTNHUUtkbEM1NXZNeTNwK09WbDNNbEc3RWNTVUpMdApwZ2ZKaWw3L3dTWWhpUnhJU3hrYkk5cWhvNEwzZm5PZVB3clFVd2FzU1ZiL1lxbHZ2WHM9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K" + expectedTok = "myRootToken" + expectedTokMountPath = "./vault.txt" ) func TestReadVaultToken(t *testing.T) { @@ -60,6 +62,17 @@ func TestReadVaultToken(t *testing.T) { assert.Nil(t, err) assert.NotEqual(t, "thisistheroottoken", token) }) + + t.Run("read token from vaultToken", func(t *testing.T) { + v := vaultSecretStore{ + vaultToken: expectedTok, + } + + actualToken, err := v.readVaultToken() + + assert.Nil(t, err) + assert.Equal(t, expectedTok, actualToken) + }) } func TestVaultTLSConfig(t *testing.T) { @@ -84,6 +97,99 @@ func TestVaultTLSConfig(t *testing.T) { }) } +func TestVaultTokenMountPathOrVaultTokenRequired(t *testing.T) { + t.Run("without vaultTokenMount or vaultToken", func(t *testing.T) { + properties := map[string]string{} + + m := secretstores.Metadata{ + Properties: properties, + } + + target := &vaultSecretStore{ + client: nil, + logger: nil, + } + + err := target.Init(m) + + assert.Equal(t, "", target.vaultToken) + assert.Equal(t, "", target.vaultTokenMountPath) + assert.NotNil(t, err) + assert.Equal(t, "token mount path and token not set", err.Error()) + }) + + t.Run("with vaultTokenMount", func(t *testing.T) { + properties := map[string]string{ + "vaultTokenMountPath": expectedTokMountPath, + } + + m := secretstores.Metadata{ + Properties: properties, + } + + target := &vaultSecretStore{ + client: nil, + logger: nil, + } + + // This call will throw an error on Windows systems because of the of + // the call x509.SystemCertPool() because system root pool is not + // available on Windows so ignore the error for when the tests are run + // on the Windows platform during CI + _ = target.Init(m) + + assert.Equal(t, "", target.vaultToken) + assert.Equal(t, expectedTokMountPath, target.vaultTokenMountPath) + }) + + t.Run("with vaultToken", func(t *testing.T) { + properties := map[string]string{ + "vaultToken": expectedTok, + } + + m := secretstores.Metadata{ + Properties: properties, + } + + target := &vaultSecretStore{ + client: nil, + logger: nil, + } + + // This call will throw an error on Windows systems because of the of + // the call x509.SystemCertPool() because system root pool is not + // available on Windows so ignore the error for when the tests are run + // on the Windows platform during CI + _ = target.Init(m) + + assert.Equal(t, "", target.vaultTokenMountPath) + assert.Equal(t, expectedTok, target.vaultToken) + }) + + t.Run("with vaultTokenMount and vaultToken", func(t *testing.T) { + properties := map[string]string{ + "vaultToken": expectedTok, + "vaultTokenMountPath": expectedTokMountPath, + } + + m := secretstores.Metadata{ + Properties: properties, + } + + target := &vaultSecretStore{ + client: nil, + logger: nil, + } + + err := target.Init(m) + + assert.Equal(t, expectedTok, target.vaultToken) + assert.Equal(t, expectedTokMountPath, target.vaultTokenMountPath) + assert.NotNil(t, err) + assert.Equal(t, "token mount path and token both set", err.Error()) + }) +} + func TestDefaultVaultAddress(t *testing.T) { t.Run("with blank vaultAddr", func(t *testing.T) { properties := map[string]string{