Skip to content

Commit

Permalink
refactor: move remote code to separate file
Browse files Browse the repository at this point in the history
Signed-off-by: Mark Sagi-Kazar <[email protected]>
  • Loading branch information
sagikazarmark committed Jun 2, 2024
1 parent 6557674 commit 810cf5c
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 244 deletions.
255 changes: 255 additions & 0 deletions remote.go
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
}
Loading

0 comments on commit 810cf5c

Please sign in to comment.