diff --git a/relay/config/dynamic_config.go b/relay/config/dynamic_config.go index c358835..336a57e 100644 --- a/relay/config/dynamic_config.go +++ b/relay/config/dynamic_config.go @@ -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'.", diff --git a/relay/messages/env_builder.go b/relay/messages/env_builder.go index fea32b0..e57d21f 100644 --- a/relay/messages/env_builder.go +++ b/relay/messages/env_builder.go @@ -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))