-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: move remote code to separate file
Signed-off-by: Mark Sagi-Kazar <[email protected]>
- Loading branch information
1 parent
7162e92
commit 272344e
Showing
2 changed files
with
257 additions
and
244 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
package viper | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"reflect" | ||
) | ||
|
||
// SupportedRemoteProviders are universally supported remote providers. | ||
var SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore", "nats"} | ||
|
||
func resetRemote() { | ||
SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore", "nats"} | ||
} | ||
|
||
type remoteConfigFactory interface { | ||
Get(rp RemoteProvider) (io.Reader, error) | ||
Watch(rp RemoteProvider) (io.Reader, error) | ||
WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool) | ||
} | ||
|
||
type RemoteResponse struct { | ||
Value []byte | ||
Error error | ||
} | ||
|
||
// RemoteConfig is optional, see the remote package. | ||
var RemoteConfig remoteConfigFactory | ||
|
||
// UnsupportedRemoteProviderError denotes encountering an unsupported remote | ||
// provider. Currently only etcd and Consul are supported. | ||
type UnsupportedRemoteProviderError string | ||
|
||
// Error returns the formatted remote provider error. | ||
func (str UnsupportedRemoteProviderError) Error() string { | ||
return fmt.Sprintf("Unsupported Remote Provider Type %q", string(str)) | ||
} | ||
|
||
// RemoteConfigError denotes encountering an error while trying to | ||
// pull the configuration from the remote provider. | ||
type RemoteConfigError string | ||
|
||
// Error returns the formatted remote provider error. | ||
func (rce RemoteConfigError) Error() string { | ||
return fmt.Sprintf("Remote Configurations Error: %s", string(rce)) | ||
} | ||
|
||
type defaultRemoteProvider struct { | ||
provider string | ||
endpoint string | ||
path string | ||
secretKeyring string | ||
} | ||
|
||
func (rp defaultRemoteProvider) Provider() string { | ||
return rp.provider | ||
} | ||
|
||
func (rp defaultRemoteProvider) Endpoint() string { | ||
return rp.endpoint | ||
} | ||
|
||
func (rp defaultRemoteProvider) Path() string { | ||
return rp.path | ||
} | ||
|
||
func (rp defaultRemoteProvider) SecretKeyring() string { | ||
return rp.secretKeyring | ||
} | ||
|
||
// RemoteProvider stores the configuration necessary | ||
// to connect to a remote key/value store. | ||
// Optional secretKeyring to unencrypt encrypted values | ||
// can be provided. | ||
type RemoteProvider interface { | ||
Provider() string | ||
Endpoint() string | ||
Path() string | ||
SecretKeyring() string | ||
} | ||
|
||
// AddRemoteProvider adds a remote configuration source. | ||
// Remote Providers are searched in the order they are added. | ||
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported. | ||
// endpoint is the url. etcd requires http://ip:port, consul requires ip:port, nats requires nats://ip:port | ||
// path is the path in the k/v store to retrieve configuration | ||
// To retrieve a config file called myapp.json from /configs/myapp.json | ||
// you should set path to /configs and set config name (SetConfigName()) to | ||
// "myapp". | ||
func AddRemoteProvider(provider, endpoint, path string) error { | ||
return v.AddRemoteProvider(provider, endpoint, path) | ||
} | ||
|
||
func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error { | ||
if !stringInSlice(provider, SupportedRemoteProviders) { | ||
return UnsupportedRemoteProviderError(provider) | ||
} | ||
if provider != "" && endpoint != "" { | ||
v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint) | ||
|
||
rp := &defaultRemoteProvider{ | ||
endpoint: endpoint, | ||
provider: provider, | ||
path: path, | ||
} | ||
if !v.providerPathExists(rp) { | ||
v.remoteProviders = append(v.remoteProviders, rp) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// AddSecureRemoteProvider adds a remote configuration source. | ||
// Secure Remote Providers are searched in the order they are added. | ||
// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported. | ||
// endpoint is the url. etcd requires http://ip:port consul requires ip:port | ||
// secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg | ||
// path is the path in the k/v store to retrieve configuration | ||
// To retrieve a config file called myapp.json from /configs/myapp.json | ||
// you should set path to /configs and set config name (SetConfigName()) to | ||
// "myapp". | ||
// Secure Remote Providers are implemented with github.com/sagikazarmark/crypt. | ||
func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error { | ||
return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring) | ||
} | ||
|
||
func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error { | ||
if !stringInSlice(provider, SupportedRemoteProviders) { | ||
return UnsupportedRemoteProviderError(provider) | ||
} | ||
if provider != "" && endpoint != "" { | ||
v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint) | ||
|
||
rp := &defaultRemoteProvider{ | ||
endpoint: endpoint, | ||
provider: provider, | ||
path: path, | ||
secretKeyring: secretkeyring, | ||
} | ||
if !v.providerPathExists(rp) { | ||
v.remoteProviders = append(v.remoteProviders, rp) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool { | ||
for _, y := range v.remoteProviders { | ||
if reflect.DeepEqual(y, p) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// ReadRemoteConfig attempts to get configuration from a remote source | ||
// and read it in the remote configuration registry. | ||
func ReadRemoteConfig() error { return v.ReadRemoteConfig() } | ||
|
||
func (v *Viper) ReadRemoteConfig() error { | ||
return v.getKeyValueConfig() | ||
} | ||
|
||
func WatchRemoteConfig() error { return v.WatchRemoteConfig() } | ||
func (v *Viper) WatchRemoteConfig() error { | ||
return v.watchKeyValueConfig() | ||
} | ||
|
||
func (v *Viper) WatchRemoteConfigOnChannel() error { | ||
return v.watchKeyValueConfigOnChannel() | ||
} | ||
|
||
// Retrieve the first found remote configuration. | ||
func (v *Viper) getKeyValueConfig() error { | ||
if RemoteConfig == nil { | ||
return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/spf13/viper/remote'") | ||
} | ||
|
||
if len(v.remoteProviders) == 0 { | ||
return RemoteConfigError("No Remote Providers") | ||
} | ||
|
||
for _, rp := range v.remoteProviders { | ||
val, err := v.getRemoteConfig(rp) | ||
if err != nil { | ||
v.logger.Error(fmt.Errorf("get remote config: %w", err).Error()) | ||
|
||
continue | ||
} | ||
|
||
v.kvstore = val | ||
|
||
return nil | ||
} | ||
return RemoteConfigError("No Files Found") | ||
} | ||
|
||
func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]any, error) { | ||
reader, err := RemoteConfig.Get(provider) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = v.unmarshalReader(reader, v.kvstore) | ||
return v.kvstore, err | ||
} | ||
|
||
// Retrieve the first found remote configuration. | ||
func (v *Viper) watchKeyValueConfigOnChannel() error { | ||
if len(v.remoteProviders) == 0 { | ||
return RemoteConfigError("No Remote Providers") | ||
} | ||
|
||
for _, rp := range v.remoteProviders { | ||
respc, _ := RemoteConfig.WatchChannel(rp) | ||
// Todo: Add quit channel | ||
go func(rc <-chan *RemoteResponse) { | ||
for { | ||
b := <-rc | ||
reader := bytes.NewReader(b.Value) | ||
v.unmarshalReader(reader, v.kvstore) | ||
} | ||
}(respc) | ||
return nil | ||
} | ||
return RemoteConfigError("No Files Found") | ||
} | ||
|
||
// Retrieve the first found remote configuration. | ||
func (v *Viper) watchKeyValueConfig() error { | ||
if len(v.remoteProviders) == 0 { | ||
return RemoteConfigError("No Remote Providers") | ||
} | ||
|
||
for _, rp := range v.remoteProviders { | ||
val, err := v.watchRemoteConfig(rp) | ||
if err != nil { | ||
v.logger.Error(fmt.Errorf("watch remote config: %w", err).Error()) | ||
|
||
continue | ||
} | ||
v.kvstore = val | ||
return nil | ||
} | ||
return RemoteConfigError("No Files Found") | ||
} | ||
|
||
func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]any, error) { | ||
reader, err := RemoteConfig.Watch(provider) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = v.unmarshalReader(reader, v.kvstore) | ||
return v.kvstore, err | ||
} |
Oops, something went wrong.