Move struct DynamicOptions to package pkg/server (#5625)

* move struct DynamicOptions to package pkg/server/dynamic_options.go

Signed-off-by: wenhaozhou <wenhaozhou@yunify.com>

* update test types

Signed-off-by: wenhaozhou <wenhaozhou@yunify.com>

---------

Signed-off-by: wenhaozhou <wenhaozhou@yunify.com>
This commit is contained in:
Wenhao Zhou
2023-04-07 11:33:36 +08:00
committed by GitHub
parent c57a89af3c
commit 62427cda32
21 changed files with 139 additions and 122 deletions

View File

@@ -27,7 +27,7 @@ import (
"golang.org/x/oauth2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/server/options"
)
func init() {
@@ -89,9 +89,9 @@ func (f *idaasProviderFactory) Type() string {
return "AliyunIDaaSProvider"
}
func (f *idaasProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
func (f *idaasProviderFactory) Create(opts options.DynamicOptions) (identityprovider.OAuthProvider, error) {
var idaas aliyunIDaaS
if err := mapstructure.Decode(options, &idaas); err != nil {
if err := mapstructure.Decode(opts, &idaas); err != nil {
return nil, err
}
idaas.Config = &oauth2.Config{

View File

@@ -24,16 +24,16 @@ import (
"gopkg.in/yaml.v3"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/server/options"
)
func Test_idaasProviderFactory_Create(t *testing.T) {
type args struct {
options oauth.DynamicOptions
options options.DynamicOptions
}
mustUnmarshalYAML := func(data string) oauth.DynamicOptions {
var dynamicOptions oauth.DynamicOptions
mustUnmarshalYAML := func(data string) options.DynamicOptions {
var dynamicOptions options.DynamicOptions
_ = yaml.Unmarshal([]byte(data), &dynamicOptions)
return dynamicOptions
}

View File

@@ -26,7 +26,7 @@ import (
gocas "gopkg.in/cas.v2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/server/options"
)
func init() {
@@ -63,9 +63,9 @@ func (f casProviderFactory) Type() string {
return "CASIdentityProvider"
}
func (f casProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
func (f casProviderFactory) Create(opts options.DynamicOptions) (identityprovider.OAuthProvider, error) {
var cas cas
if err := mapstructure.Decode(options, &cas); err != nil {
if err := mapstructure.Decode(opts, &cas); err != nil {
return nil, err
}
casURL, err := url.Parse(cas.CASServerURL)

View File

@@ -19,7 +19,7 @@
package identityprovider
import (
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/server/options"
)
type GenericProvider interface {
@@ -31,5 +31,5 @@ type GenericProviderFactory interface {
// Type unique type of the provider
Type() string
// Apply the dynamic options from kubesphere-config
Create(options oauth.DynamicOptions) (GenericProvider, error)
Create(options options.DynamicOptions) (GenericProvider, error)
}

View File

@@ -28,7 +28,7 @@ import (
"golang.org/x/oauth2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/server/options"
)
const (
@@ -121,9 +121,9 @@ func (g *ldapProviderFactory) Type() string {
return "GitHubIdentityProvider"
}
func (g *ldapProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
func (g *ldapProviderFactory) Create(opts options.DynamicOptions) (identityprovider.OAuthProvider, error) {
var github github
if err := mapstructure.Decode(options, &github); err != nil {
if err := mapstructure.Decode(opts, &github); err != nil {
return nil, err
}
@@ -137,7 +137,7 @@ func (g *ldapProviderFactory) Create(options oauth.DynamicOptions) (identityprov
github.Endpoint.UserInfoURL = userInfoURL
}
// fixed options
options["endpoint"] = oauth.DynamicOptions{
opts["endpoint"] = options.DynamicOptions{
"authURL": github.Endpoint.AuthURL,
"tokenURL": github.Endpoint.TokenURL,
"userInfoURL": github.Endpoint.UserInfoURL,

View File

@@ -27,6 +27,8 @@ import (
"testing"
"time"
"kubesphere.io/kubesphere/pkg/server/options"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
@@ -34,7 +36,6 @@ import (
"gopkg.in/yaml.v3"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
)
var githubServer *httptest.Server
@@ -119,12 +120,12 @@ scopes:
Expect(provider).Should(Equal(expected))
})
It("should configure successfully", func() {
config := oauth.DynamicOptions{
config := options.DynamicOptions{
"clientID": "de6ff8bed0304e487b6e",
"clientSecret": "2b70536f79ec8d2939863509d05e2a71c268b9af",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
"insecureSkipVerify": true,
"endpoint": oauth.DynamicOptions{
"endpoint": options.DynamicOptions{
"authURL": fmt.Sprintf("%s/login/oauth/authorize", githubServer.URL),
"tokenURL": fmt.Sprintf("%s/login/oauth/access_token", githubServer.URL),
"userInfoURL": fmt.Sprintf("%s/user", githubServer.URL),
@@ -133,12 +134,12 @@ scopes:
factory := ldapProviderFactory{}
provider, err = factory.Create(config)
Expect(err).Should(BeNil())
expected := oauth.DynamicOptions{
expected := options.DynamicOptions{
"clientID": "de6ff8bed0304e487b6e",
"clientSecret": "2b70536f79ec8d2939863509d05e2a71c268b9af",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/github",
"insecureSkipVerify": true,
"endpoint": oauth.DynamicOptions{
"endpoint": options.DynamicOptions{
"authURL": fmt.Sprintf("%s/login/oauth/authorize", githubServer.URL),
"tokenURL": fmt.Sprintf("%s/login/oauth/access_token", githubServer.URL),
"userInfoURL": fmt.Sprintf("%s/user", githubServer.URL),
@@ -158,8 +159,8 @@ scopes:
})
})
func mustUnmarshalYAML(data string) oauth.DynamicOptions {
var dynamicOptions oauth.DynamicOptions
func mustUnmarshalYAML(data string) options.DynamicOptions {
var dynamicOptions options.DynamicOptions
_ = yaml.Unmarshal([]byte(data), &dynamicOptions)
return dynamicOptions
}

View File

@@ -22,6 +22,8 @@ import (
"net/http"
"testing"
"kubesphere.io/kubesphere/pkg/server/options"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
)
@@ -55,7 +57,7 @@ func (e emptyOAuthProvider) IdentityExchangeCallback(req *http.Request) (Identit
return emptyIdentity{}, nil
}
func (e emptyOAuthProviderFactory) Create(options oauth.DynamicOptions) (OAuthProvider, error) {
func (e emptyOAuthProviderFactory) Create(options options.DynamicOptions) (OAuthProvider, error) {
return emptyOAuthProvider{}, nil
}
@@ -74,7 +76,7 @@ func (e emptyGenericProvider) Authenticate(username string, password string) (Id
return emptyIdentity{}, nil
}
func (e emptyGenericProviderFactory) Create(options oauth.DynamicOptions) (GenericProvider, error) {
func (e emptyGenericProviderFactory) Create(options options.DynamicOptions) (GenericProvider, error) {
return emptyGenericProvider{}, nil
}
@@ -97,7 +99,7 @@ func TestSetupWith(t *testing.T) {
Name: "ldap",
MappingMethod: "auto",
Type: "LDAPIdentityProvider",
Provider: oauth.DynamicOptions{},
Provider: options.DynamicOptions{},
},
}},
wantErr: false,
@@ -109,7 +111,7 @@ func TestSetupWith(t *testing.T) {
Name: "ldap",
MappingMethod: "auto",
Type: "LDAPIdentityProvider",
Provider: oauth.DynamicOptions{},
Provider: options.DynamicOptions{},
},
}},
wantErr: true,
@@ -121,7 +123,7 @@ func TestSetupWith(t *testing.T) {
Name: "test",
MappingMethod: "auto",
Type: "NotSupported",
Provider: oauth.DynamicOptions{},
Provider: options.DynamicOptions{},
},
}},
wantErr: true,

View File

@@ -30,7 +30,7 @@ import (
"k8s.io/klog/v2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/server/options"
)
const (
@@ -85,9 +85,9 @@ func (l *ldapProviderFactory) Type() string {
return ldapIdentityProvider
}
func (l *ldapProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.GenericProvider, error) {
func (l *ldapProviderFactory) Create(opts options.DynamicOptions) (identityprovider.GenericProvider, error) {
var ldapProvider ldapProvider
if err := mapstructure.Decode(options, &ldapProvider); err != nil {
if err := mapstructure.Decode(opts, &ldapProvider); err != nil {
return nil, err
}
if ldapProvider.ReadTimeout <= 0 {

View File

@@ -20,14 +20,14 @@ import (
"os"
"testing"
"kubesphere.io/kubesphere/pkg/server/options"
"github.com/google/go-cmp/cmp"
"gopkg.in/yaml.v3"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
)
func TestNewLdapProvider(t *testing.T) {
options := `
opts := `
host: test.sn.mynetname.net:389
managerDN: uid=root,cn=users,dc=test,dc=sn,dc=mynetname,dc=net
managerPassword: test
@@ -36,8 +36,8 @@ userSearchBase: dc=test,dc=sn,dc=mynetname,dc=net
loginAttribute: uid
mailAttribute: mail
`
var dynamicOptions oauth.DynamicOptions
err := yaml.Unmarshal([]byte(options), &dynamicOptions)
var dynamicOptions options.DynamicOptions
err := yaml.Unmarshal([]byte(opts), &dynamicOptions)
if err != nil {
t.Fatal(err)
}
@@ -73,12 +73,12 @@ func TestLdapProvider_Authenticate(t *testing.T) {
if configFile == "" {
t.Skip("Skipped")
}
options, err := os.ReadFile(configFile)
opts, err := os.ReadFile(configFile)
if err != nil {
t.Fatal(err)
}
var dynamicOptions oauth.DynamicOptions
if err = yaml.Unmarshal(options, &dynamicOptions); err != nil {
var dynamicOptions options.DynamicOptions
if err = yaml.Unmarshal(opts, &dynamicOptions); err != nil {
t.Fatal(err)
}
ldapProvider, err := new(ldapProviderFactory).Create(dynamicOptions)

View File

@@ -19,7 +19,7 @@ package identityprovider
import (
"net/http"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/server/options"
)
type OAuthProvider interface {
@@ -31,5 +31,5 @@ type OAuthProviderFactory interface {
// Type unique type of the provider
Type() string
// Create Apply the dynamic options
Create(options oauth.DynamicOptions) (OAuthProvider, error)
Create(options options.DynamicOptions) (OAuthProvider, error)
}

View File

@@ -25,15 +25,14 @@ import (
"io"
"net/http"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"github.com/coreos/go-oidc"
"github.com/golang-jwt/jwt/v4"
"github.com/mitchellh/mapstructure"
"golang.org/x/oauth2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
"kubesphere.io/kubesphere/pkg/server/options"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
)
func init() {
@@ -133,9 +132,9 @@ func (f *oidcProviderFactory) Type() string {
return "OIDCIdentityProvider"
}
func (f *oidcProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
func (f *oidcProviderFactory) Create(opts options.DynamicOptions) (identityprovider.OAuthProvider, error) {
var oidcProvider oidcProvider
if err := mapstructure.Decode(options, &oidcProvider); err != nil {
if err := mapstructure.Decode(opts, &oidcProvider); err != nil {
return nil, err
}
// dynamically discover
@@ -169,7 +168,7 @@ func (f *oidcProviderFactory) Create(options oauth.DynamicOptions) (identityprov
// TODO: support HS256
ClientID: oidcProvider.ClientID,
})
options["endpoint"] = oauth.DynamicOptions{
opts["endpoint"] = options.DynamicOptions{
"authURL": oidcProvider.Endpoint.AuthURL,
"tokenURL": oidcProvider.Endpoint.TokenURL,
"userInfoURL": oidcProvider.Endpoint.UserInfoURL,

View File

@@ -33,6 +33,8 @@ import (
"testing"
"time"
"kubesphere.io/kubesphere/pkg/server/options"
"github.com/golang-jwt/jwt/v4"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -40,7 +42,6 @@ import (
"gopkg.in/square/go-jose.v2"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/identityprovider"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
)
var (
@@ -167,7 +168,7 @@ var _ = Describe("OIDC", func() {
err error
)
It("should configure successfully", func() {
config := oauth.DynamicOptions{
config := options.DynamicOptions{
"issuer": oidcServer.URL,
"clientID": "kubesphere",
"clientSecret": "c53e80ab92d48ab12f4e7f1f6976d1bdc996e0d7",
@@ -177,13 +178,13 @@ var _ = Describe("OIDC", func() {
factory := oidcProviderFactory{}
provider, err = factory.Create(config)
Expect(err).Should(BeNil())
expected := oauth.DynamicOptions{
expected := options.DynamicOptions{
"issuer": oidcServer.URL,
"clientID": "kubesphere",
"clientSecret": "c53e80ab92d48ab12f4e7f1f6976d1bdc996e0d7",
"redirectURL": "https://ks-console.kubesphere-system.svc/oauth/redirect/oidc",
"insecureSkipVerify": true,
"endpoint": oauth.DynamicOptions{
"endpoint": options.DynamicOptions{
"authURL": fmt.Sprintf("%s/authorize", oidcServer.URL),
"tokenURL": fmt.Sprintf("%s/token", oidcServer.URL),
"userInfoURL": fmt.Sprintf("%s/userinfo", oidcServer.URL),

View File

@@ -17,12 +17,12 @@ limitations under the License.
package oauth
import (
"encoding/json"
"errors"
"net/url"
"strings"
"time"
"kubesphere.io/kubesphere/pkg/server/options"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
)
@@ -91,57 +91,6 @@ type Options struct {
AccessTokenInactivityTimeout time.Duration `json:"accessTokenInactivityTimeout" yaml:"accessTokenInactivityTimeout"`
}
// DynamicOptions accept dynamic configuration, the type of key MUST be string
type DynamicOptions map[string]interface{}
func (o DynamicOptions) MarshalJSON() ([]byte, error) {
data, err := json.Marshal(desensitize(o))
return data, err
}
var (
sensitiveKeys = [...]string{"password", "secret"}
)
// isSensitiveData returns whether the input string contains sensitive information
func isSensitiveData(key string) bool {
for _, v := range sensitiveKeys {
if strings.Contains(strings.ToLower(key), v) {
return true
}
}
return false
}
// desensitize returns the desensitized data
func desensitize(data map[string]interface{}) map[string]interface{} {
output := make(map[string]interface{})
for k, v := range data {
if isSensitiveData(k) {
continue
}
switch v := v.(type) {
case map[interface{}]interface{}:
output[k] = desensitize(convert(v))
default:
output[k] = v
}
}
return output
}
// convert returns formatted data
func convert(m map[interface{}]interface{}) map[string]interface{} {
output := make(map[string]interface{})
for k, v := range m {
switch k := k.(type) {
case string:
output[k] = v
}
}
return output
}
type IdentityProviderOptions struct {
// The provider name.
Name string `json:"name" yaml:"name"`
@@ -164,7 +113,7 @@ type IdentityProviderOptions struct {
Type string `json:"type" yaml:"type"`
// The options of identify provider
Provider DynamicOptions `json:"provider" yaml:"provider"`
Provider options.DynamicOptions `json:"provider" yaml:"provider"`
}
type Token struct {

View File

@@ -22,6 +22,8 @@ import (
"reflect"
"testing"
"kubesphere.io/kubesphere/pkg/server/options"
"kubesphere.io/kubesphere/pkg/apiserver/authentication"
"github.com/mitchellh/mapstructure"
@@ -44,7 +46,7 @@ func Test_oauthAuthenticator_Authenticate(t *testing.T) {
Name: "fake",
MappingMethod: "auto",
Type: "FakeIdentityProvider",
Provider: oauth.DynamicOptions{
Provider: options.DynamicOptions{
"identities": map[string]interface{}{
"code1": map[string]string{
"uid": "100001",
@@ -213,9 +215,9 @@ func (fakeProviderFactory) Type() string {
return "FakeIdentityProvider"
}
func (fakeProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.OAuthProvider, error) {
func (fakeProviderFactory) Create(dynamicOptions options.DynamicOptions) (identityprovider.OAuthProvider, error) {
var fakeProvider fakeProvider
if err := mapstructure.Decode(options, &fakeProvider); err != nil {
if err := mapstructure.Decode(dynamicOptions, &fakeProvider); err != nil {
return nil, err
}
return &fakeProvider, nil

View File

@@ -23,6 +23,8 @@ import (
"reflect"
"testing"
"kubesphere.io/kubesphere/pkg/server/options"
"github.com/mitchellh/mapstructure"
"golang.org/x/crypto/bcrypt"
"k8s.io/apimachinery/pkg/api/errors"
@@ -62,7 +64,7 @@ func Test_passwordAuthenticator_Authenticate(t *testing.T) {
Name: "fakepwd",
MappingMethod: "auto",
Type: "fakePasswordProvider",
Provider: oauth.DynamicOptions{
Provider: options.DynamicOptions{
"identities": map[string]interface{}{
"user1": map[string]string{
"uid": "100001",
@@ -84,7 +86,7 @@ func Test_passwordAuthenticator_Authenticate(t *testing.T) {
MappingMethod: "auto",
Type: "fakePasswordProvider",
DisableLoginConfirmation: true,
Provider: oauth.DynamicOptions{
Provider: options.DynamicOptions{
"identities": map[string]interface{}{
"user5": map[string]string{
"uid": "100005",
@@ -100,7 +102,7 @@ func Test_passwordAuthenticator_Authenticate(t *testing.T) {
MappingMethod: "lookup",
Type: "fakePasswordProvider",
DisableLoginConfirmation: true,
Provider: oauth.DynamicOptions{
Provider: options.DynamicOptions{
"identities": map[string]interface{}{
"user6": map[string]string{
"uid": "100006",
@@ -276,9 +278,9 @@ func (fakePasswordProviderFactory) Type() string {
return "fakePasswordProvider"
}
func (fakePasswordProviderFactory) Create(options oauth.DynamicOptions) (identityprovider.GenericProvider, error) {
func (fakePasswordProviderFactory) Create(dynamicOptions options.DynamicOptions) (identityprovider.GenericProvider, error) {
var fakeProvider fakePasswordProvider
if err := mapstructure.Decode(options, &fakeProvider); err != nil {
if err := mapstructure.Decode(dynamicOptions, &fakeProvider); err != nil {
return nil, err
}
return &fakeProvider, nil

View File

@@ -0,0 +1,57 @@
package options
import (
"encoding/json"
"strings"
)
// DynamicOptions accept dynamic configuration, the type of key MUST be string
type DynamicOptions map[string]interface{}
func (o DynamicOptions) MarshalJSON() ([]byte, error) {
data, err := json.Marshal(desensitize(o))
return data, err
}
var (
sensitiveKeys = [...]string{"password", "secret"}
)
// isSensitiveData returns whether the input string contains sensitive information
func isSensitiveData(key string) bool {
for _, v := range sensitiveKeys {
if strings.Contains(strings.ToLower(key), v) {
return true
}
}
return false
}
// desensitize returns the desensitized data
func desensitize(data map[string]interface{}) map[string]interface{} {
output := make(map[string]interface{})
for k, v := range data {
if isSensitiveData(k) {
continue
}
switch v := v.(type) {
case map[interface{}]interface{}:
output[k] = desensitize(convert(v))
default:
output[k] = v
}
}
return output
}
// convert returns formatted data
func convert(m map[interface{}]interface{}) map[string]interface{} {
output := make(map[string]interface{})
for k, v := range m {
switch k := k.(type) {
case string:
output[k] = v
}
}
return output
}

View File

@@ -49,10 +49,6 @@ type Interface interface {
Expire(key string, duration time.Duration) error
}
// DynamicOptions the options of the cache. For redis, options key can be "host", "port", "db", "password".
// For InMemoryCache, options key can be "cleanupperiod"
type DynamicOptions map[string]interface{}
func RegisterCacheFactory(factory CacheFactory) {
cacheFactories[factory.Type()] = factory
}

View File

@@ -1,8 +1,10 @@
package cache
import "kubesphere.io/kubesphere/pkg/server/options"
type CacheFactory interface {
// Type unique type of the cache
Type() string
// Create relevant caches by type
Create(options DynamicOptions, stopCh <-chan struct{}) (Interface, error)
Create(options options.DynamicOptions, stopCh <-chan struct{}) (Interface, error)
}

View File

@@ -21,6 +21,8 @@ import (
"strings"
"time"
"kubesphere.io/kubesphere/pkg/server/options"
"github.com/mitchellh/mapstructure"
"k8s.io/apimachinery/pkg/util/wait"
@@ -177,7 +179,7 @@ func (sf *inMemoryCacheFactory) Type() string {
return typeInMemoryCache
}
func (sf *inMemoryCacheFactory) Create(options DynamicOptions, stopCh <-chan struct{}) (Interface, error) {
func (sf *inMemoryCacheFactory) Create(options options.DynamicOptions, stopCh <-chan struct{}) (Interface, error) {
var sOptions InMemoryCacheOptions
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{

View File

@@ -18,11 +18,13 @@ package cache
import (
"fmt"
"kubesphere.io/kubesphere/pkg/server/options"
)
type Options struct {
Type string `json:"type"`
Options DynamicOptions `json:"options"`
Type string `json:"type"`
Options options.DynamicOptions `json:"options"`
}
// NewCacheOptions returns options points to nowhere,

View File

@@ -24,6 +24,8 @@ import (
"github.com/go-redis/redis"
"github.com/mitchellh/mapstructure"
"k8s.io/klog/v2"
"kubesphere.io/kubesphere/pkg/server/options"
)
const typeRedis = "redis"
@@ -108,7 +110,7 @@ func (rf *redisFactory) Type() string {
return typeRedis
}
func (rf *redisFactory) Create(options DynamicOptions, stopCh <-chan struct{}) (Interface, error) {
func (rf *redisFactory) Create(options options.DynamicOptions, stopCh <-chan struct{}) (Interface, error) {
var rOptions redisOptions
if err := mapstructure.Decode(options, &rOptions); err != nil {
return nil, err