| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package exec |
| |
| import ( |
| "encoding/base64" |
| "encoding/json" |
| "fmt" |
| "sync" |
| |
| "v.io/v23/verror" |
| "v.io/v23/vom" |
| ) |
| |
| // Config defines a simple key-value configuration. Keys and values are |
| // strings, and a key can have exactly one value. The client is responsible for |
| // encoding structured values, or multiple values, in the provided string. |
| // |
| // Config data can come from several sources: |
| // - passed from parent process to child process through pipe; |
| // - using environment variables or flags; |
| // - via the neighborhood-based config service; |
| // - by RPCs using the Config idl; |
| // - manually, by calling the Set method. |
| // |
| // This interface makes no assumptions about the source of the configuration, |
| // but provides a unified API for accessing it. |
| type Config interface { |
| // Set sets the value for the key. If the key already exists in the |
| // config, its value is overwritten. |
| Set(key, value string) |
| // Get returns the value for the key. If the key doesn't exist |
| // in the config, Get returns an error. |
| Get(key string) (string, error) |
| // Clear removes the specified key from the config. |
| Clear(key string) |
| // Serialize serializes the config to a string. |
| Serialize() (string, error) |
| // MergeFrom deserializes config information from a string created using |
| // Serialize(), and merges this information into the config, updating |
| // values for keys that already exist and creating new key-value pairs |
| // for keys that don't. |
| MergeFrom(string) error |
| // Dump returns the config information as a map from ket to value. |
| Dump() map[string]string |
| } |
| |
| type cfg struct { |
| sync.RWMutex |
| m map[string]string |
| } |
| |
| // New creates a new empty config. |
| func NewConfig() Config { |
| return &cfg{m: make(map[string]string)} |
| } |
| |
| func (c *cfg) Set(key, value string) { |
| c.Lock() |
| defer c.Unlock() |
| c.m[key] = value |
| } |
| |
| func (c *cfg) Get(key string) (string, error) { |
| c.RLock() |
| defer c.RUnlock() |
| v, ok := c.m[key] |
| if !ok { |
| return "", verror.New(verror.ErrNoExist, nil, "config.Get", key) |
| } |
| return v, nil |
| } |
| |
| func (c *cfg) Dump() (res map[string]string) { |
| res = make(map[string]string) |
| c.RLock() |
| defer c.RUnlock() |
| for k, v := range c.m { |
| res[k] = v |
| } |
| return |
| } |
| |
| func (c *cfg) Clear(key string) { |
| c.Lock() |
| defer c.Unlock() |
| delete(c.m, key) |
| } |
| |
| func (c *cfg) Serialize() (string, error) { |
| c.RLock() |
| data, err := vom.Encode(c.m) |
| c.RUnlock() |
| if err != nil { |
| return "", err |
| } |
| return string(data), nil |
| } |
| |
| func (c *cfg) MergeFrom(serialized string) error { |
| var newM map[string]string |
| if err := vom.Decode([]byte(serialized), &newM); err != nil { |
| return err |
| } |
| c.Lock() |
| for k, v := range newM { |
| c.m[k] = v |
| } |
| c.Unlock() |
| return nil |
| } |
| |
| func (c *cfg) MarshalJSON() ([]byte, error) { |
| c.RLock() |
| defer c.RUnlock() |
| return json.Marshal(c.m) |
| } |
| |
| func (c *cfg) UnmarshalJSON(buf []byte) error { |
| var newM map[string]string |
| if err := json.Unmarshal(buf, &newM); err != nil { |
| return err |
| } |
| c.RLock() |
| for k, v := range newM { |
| c.m[k] = v |
| } |
| c.RUnlock() |
| return nil |
| } |
| |
| // EncodeForEnvVar encodes the supplued config using JSON and base64 |
| // so that it can be passed as a value for an environment |
| // variable. JSON is used to allow for the greatest level of |
| // interoperability. |
| func EncodeForEnvVar(config Config) (string, error) { |
| c, ok := config.(*cfg) |
| if !ok { |
| return "", fmt.Errorf("%T is the wrong type", config) |
| } |
| s, err := json.Marshal(c) |
| if err != nil { |
| return "", err |
| } |
| return base64.StdEncoding.EncodeToString(s), nil |
| } |
| |
| // DecodeFromEnvVar decodes a base64 encoded JSON representation |
| // into the supplied config. See EncodeForEnvVar. |
| func DecodeFromEnvVar(value string, config Config) error { |
| c, ok := config.(*cfg) |
| if !ok { |
| return fmt.Errorf("%T is the wrong type", config) |
| } |
| data, err := base64.StdEncoding.DecodeString(value) |
| if err != nil { |
| return err |
| } |
| return c.UnmarshalJSON(data) |
| } |