Skip to content

Commit

Permalink
Read layered dynamic configuration from filesystem
Browse files Browse the repository at this point in the history
This pulls in room- and user-specific configuration for a bundle and
layers it on top of the base dynamic configuration for that bundle.

Only reads configuration from the filesystem; this is not hooked up to
Cog's dynamic config API yet (that still only affects the base
configuration), so this will only work for users that have direct
filesystem access to their Relay hosts (i.e., not Hosted Cog).

Fixes operable/cog#850
  • Loading branch information
christophermaier committed Jul 25, 2016
1 parent 45696f8 commit e99289b
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 21 deletions.
81 changes: 61 additions & 20 deletions relay/config/dynamic_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,90 @@ import (
"os"
"path"
"strings"
"fmt"
)

// LoadDynamicConfig loads the dyanmic configuration for a bundle if
// a) dynamic configuration is enabled and b) a config file exists for
// the requested bundle
func (c *Config) LoadDynamicConfig(bundle string) map[string]interface{} {
// the requested bundle. Room- and user-specific configurations are
// layered on top, if they exist.
//
// If any configuration files exist, but cannot be properly processed
// (read, parsed as YAML, etc), an empty map is returned.
func (c *Config) LoadDynamicConfig(bundle string, roomName string, userName string) map[string]interface{} {
retval := make(map[string]interface{})
if c.DynamicConfigRoot == "" {
log.Debugf("Dynamic config disabled.")
return retval
}
if fullPath := locateConfigFile(c.DynamicConfigRoot, bundle); fullPath != "" {
buf, err := ioutil.ReadFile(fullPath)

rootInfo, rootErr := os.Stat(c.DynamicConfigRoot)
if rootErr != nil || rootInfo.IsDir() == false {
log.Debugf("Dynamic configuration root dir not found.")
return retval
}

configs := allConfigFiles(c.DynamicConfigRoot, bundle, roomName, userName)
for _, f := range configs {
log.Debugf("Reading configuration file: %s", f)
buf, err := ioutil.ReadFile(f)
if err != nil {
log.Errorf("Reading dynamic config for bundle %s failed: %s.", bundle, err)
return retval
log.Errorf("Reading dynamic config file %s failed: %s.", f, err)
return make(map[string]interface{})
}

// by unmarshalling into the same map for each layer, we
// achieve a shallow merge of the layers, with successive
// top-level keys overwriting values previously set.
err = yaml.Unmarshal(buf, &retval)
if err != nil {
log.Errorf("Parsing dynamic config for bundle %s failed: %s.", bundle, err)
return retval
log.Errorf("Parsing dynamic config file %s failed: %s.", f, err)
return make(map[string]interface{})
}
for k := range retval {
if strings.HasPrefix(k, "COG_") || strings.HasPrefix(k, "RELAY_") {
delete(retval, k)
log.Infof("Deleted illegal key %s from dynamic config for bundle %s.", k, bundle)
}
}

for k := range retval {
if strings.HasPrefix(k, "COG_") || strings.HasPrefix(k, "RELAY_") {
delete(retval, k)
log.Infof("Deleted illegal key %s from dynamic config for bundle %s.", k, bundle)
}
}

return retval
}

func locateConfigFile(configRoot string, bundle string) string {
rootInfo, rootErr := os.Stat(configRoot)
if rootErr != nil || rootInfo.IsDir() == false {
log.Debugf("Dynamic configuration root dir not found.")
return ""
// Given the config root, a bundle name, a Cog username, and a chat
// room name, return a list of all the filenames to be consolidated,
// in the order they should be layered.
func allConfigFiles(configRoot string, bundle string, room string, user string) []string {
var configs []string

if path := resolveYAMLFile(path.Join(configRoot, bundle, "config")); path != "" {
configs = append(configs, path)
}

roomFile := fmt.Sprintf("room_%s", strings.ToLower(room))
if path := resolveYAMLFile(path.Join(configRoot, bundle, roomFile)); path != "" {
configs = append(configs, path)
}

userFile := fmt.Sprintf("user_%s", strings.ToLower(user))
if path := resolveYAMLFile(path.Join(configRoot, bundle, userFile)); path != "" {
configs = append(configs, path)
}
fullYamlPath := path.Join(configRoot, bundle, "config.yaml")
fullYmlPath := path.Join(configRoot, bundle, "config.yml")

return configs
}

// Given a base file name, return the path to either the yaml or yml
// version, returning the ".yaml" version preferentially.
func resolveYAMLFile(base string) string {

fullYamlPath := fmt.Sprint(base, ".yaml")

yamlInfo, yamlErr := os.Stat(fullYamlPath)
if yamlErr != nil || yamlInfo.IsDir() == true {
fullYmlPath := fmt.Sprint(base, ".yml")
ymlInfo, ymlErr := os.Stat(fullYmlPath)
if ymlErr != nil || ymlInfo.IsDir() == true {
log.Debugf("Dynamic config not found. Checked: '%s' and '%s'.",
Expand Down
2 changes: 1 addition & 1 deletion relay/messages/env_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (er *ExecutionRequest) compileEnvironment(request *api.ExecRequest, relayCo

foundDynamicConfig := false
if useDynamicConfig {
dyn := relayConfig.LoadDynamicConfig(er.BundleName())
dyn := relayConfig.LoadDynamicConfig(er.BundleName(), er.Room.Name, er.User.Username)
foundDynamicConfig = len(dyn) > 0
for k, v := range dyn {
request.PutEnv(k, fmt.Sprintf("%s", v))
Expand Down

0 comments on commit e99289b

Please sign in to comment.