205 lines
5.2 KiB
Go
205 lines
5.2 KiB
Go
/*
|
|
* Copyright 2020 The Dragonfly Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package dynconfig
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
type SourceType string
|
|
|
|
const (
|
|
// LocalSourceType represents read configuration from local file
|
|
LocalSourceType = "local"
|
|
|
|
// ManagerSourceType represents pulling configuration from manager
|
|
ManagerSourceType = "manager"
|
|
)
|
|
|
|
const (
|
|
defaultCacheKey = "dynconfig"
|
|
)
|
|
|
|
type strategy interface {
|
|
Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error
|
|
}
|
|
|
|
type Dynconfig struct {
|
|
sourceType SourceType
|
|
managerClient ManagerClient
|
|
localConfigPath string
|
|
cachePath string
|
|
expire time.Duration
|
|
strategy strategy
|
|
}
|
|
|
|
// Option is a functional option for configuring the dynconfig
|
|
type Option func(d *Dynconfig) error
|
|
|
|
// WithManagerClient set the manager client
|
|
func WithManagerClient(c ManagerClient) Option {
|
|
return func(d *Dynconfig) error {
|
|
if d.sourceType != ManagerSourceType {
|
|
return errors.New("the source type must be ManagerSourceType")
|
|
}
|
|
|
|
d.managerClient = c
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithLocalConfigPath set the file path
|
|
func WithLocalConfigPath(p string) Option {
|
|
return func(d *Dynconfig) error {
|
|
if d.sourceType != LocalSourceType {
|
|
return errors.New("the source type must be LocalSourceType")
|
|
}
|
|
|
|
d.localConfigPath = p
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithCachePath set the cache file path
|
|
func WithCachePath(p string) Option {
|
|
return func(d *Dynconfig) error {
|
|
if d.sourceType != ManagerSourceType {
|
|
return errors.New("the source type must be ManagerSourceType")
|
|
}
|
|
|
|
d.cachePath = p
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// WithExpireTime set the expire time for cache
|
|
func WithExpireTime(e time.Duration) Option {
|
|
return func(d *Dynconfig) error {
|
|
if d.sourceType != ManagerSourceType {
|
|
return errors.New("the source type must be ManagerSourceType")
|
|
}
|
|
|
|
d.expire = e
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// New returns a new dynconfig instance
|
|
func New(sourceType SourceType, options ...Option) (*Dynconfig, error) {
|
|
d, err := NewDynconfigWithOptions(sourceType, options...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := d.validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch sourceType {
|
|
case ManagerSourceType:
|
|
d.strategy, err = newDynconfigManager(d.expire, d.cachePath, d.managerClient)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case LocalSourceType:
|
|
d.strategy, err = newDynconfigLocal(d.localConfigPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, errors.New("unknown source type")
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
// NewDynconfigWithOptions constructs a new instance of a dynconfig with additional options.
|
|
func NewDynconfigWithOptions(sourceType SourceType, options ...Option) (*Dynconfig, error) {
|
|
d := &Dynconfig{
|
|
sourceType: sourceType,
|
|
}
|
|
|
|
for _, opt := range options {
|
|
if err := opt(d); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return d, nil
|
|
}
|
|
|
|
// validate parameters
|
|
func (d *Dynconfig) validate() error {
|
|
if d.sourceType == ManagerSourceType {
|
|
if d.managerClient == nil {
|
|
return errors.New("missing parameter ManagerClient, use method WithManagerClient to assign")
|
|
}
|
|
if d.cachePath == "" {
|
|
return errors.New("missing parameter CachePath, use method WithCachePath to assign")
|
|
}
|
|
if d.expire == 0 {
|
|
return errors.New("missing parameter Expire, use method WithExpireTime to assign")
|
|
}
|
|
}
|
|
|
|
if d.sourceType == LocalSourceType && d.localConfigPath == "" {
|
|
return errors.New("missing parameter LocalConfigPath, use method WithLocalConfigPath to assign")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Unmarshal unmarshals the config into a Struct. Make sure that the tags
|
|
// on the fields of the structure are properly set.
|
|
func (d *Dynconfig) Unmarshal(rawVal interface{}, opts ...DecoderConfigOption) error {
|
|
return d.strategy.Unmarshal(rawVal, opts...)
|
|
}
|
|
|
|
// A DecoderConfigOption can be passed to dynconfig Unmarshal to configure
|
|
// mapstructure.DecoderConfig options
|
|
type DecoderConfigOption func(*mapstructure.DecoderConfig)
|
|
|
|
// defaultDecoderConfig returns default mapstructure.DecoderConfig with support
|
|
// of time.Duration values & string slices
|
|
func defaultDecoderConfig(output interface{}, opts ...DecoderConfigOption) *mapstructure.DecoderConfig {
|
|
c := &mapstructure.DecoderConfig{
|
|
Metadata: nil,
|
|
Result: output,
|
|
WeaklyTypedInput: true,
|
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
|
mapstructure.StringToTimeDurationHookFunc(),
|
|
mapstructure.StringToSliceHookFunc(","),
|
|
),
|
|
}
|
|
for _, opt := range opts {
|
|
opt(c)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// A wrapper around mapstructure.Decode that mimics the WeakDecode functionality
|
|
func decode(input interface{}, config *mapstructure.DecoderConfig) error {
|
|
decoder, err := mapstructure.NewDecoder(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return decoder.Decode(input)
|
|
}
|