blob: c1c5a92e70b12dd8b68d1ee9312371176c044b9b [file] [log] [blame]
// 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)
}