func getTemplateResources(config Config) ([]*TemplateResource, error) { var lastError error templates := make([]*TemplateResource, 0) log.Debug("Loading template resources from confdir " + config.ConfDir) if !isFileExist(config.ConfDir) { log.Warning(fmt.Sprintf("Cannot load template resources: confdir '%s' does not exist", config.ConfDir)) return nil, nil } paths, err := recursiveFindFiles(config.ConfigDir, "*toml") if err != nil { return nil, err } if len(paths) < 1 { log.Warning("Found no templates") } for _, p := range paths { log.Debug(fmt.Sprintf("Found template: %s", p)) t, err := NewTemplateResource(p, config) if err != nil { lastError = err continue } templates = append(templates, t) } return templates, lastError }
// GetValues retrieves the values for the given keys from DynamoDB func (c *Client) GetValues(keys []string) (map[string]string, error) { vars := make(map[string]string) for _, key := range keys { // Check if we can find the single item m := make(map[string]*dynamodb.AttributeValue) m["key"] = &dynamodb.AttributeValue{S: aws.String(key)} g, err := c.client.GetItem(&dynamodb.GetItemInput{Key: m, TableName: &c.table}) if err != nil { return vars, err } if g.Item != nil { if val, ok := g.Item["value"]; ok { if val.S != nil { vars[key] = *val.S } else { log.Warning("Skipping key '%s'. 'value' is not of type 'string'.", key) } continue } } // Check for nested keys q, err := c.client.Scan( &dynamodb.ScanInput{ ScanFilter: map[string]*dynamodb.Condition{ "key": &dynamodb.Condition{ AttributeValueList: []*dynamodb.AttributeValue{ &dynamodb.AttributeValue{S: aws.String(key)}}, ComparisonOperator: aws.String("BEGINS_WITH")}}, AttributesToGet: []*string{aws.String("key"), aws.String("value")}, TableName: aws.String(c.table), Select: aws.String("SPECIFIC_ATTRIBUTES"), }) if err != nil { return vars, err } for _, i := range q.Items { item := i if val, ok := item["value"]; ok { if val.S != nil { vars[*item["key"].S] = *val.S } else { log.Warning("Skipping key '%s'. 'value' is not of type 'string'.", *item["key"].S) } continue } } } return vars, nil }
// ProcessTemplateResources is a convenience function that loads all the // template resources and processes them serially. Called from main. // It returns a list of errors if any. func ProcessTemplateResources(c etcdutil.EtcdClient) []error { runErrors := make([]error, 0) var err error if c == nil { runErrors = append(runErrors, errors.New("An etcd client is required")) return runErrors } log.Debug("Loading template resources from confdir " + config.ConfDir()) if !isFileExist(config.ConfDir()) { log.Warning(fmt.Sprintf("Cannot load template resources confdir '%s' does not exist", config.ConfDir())) return runErrors } paths, err := filepath.Glob(filepath.Join(config.ConfigDir(), "*toml")) if err != nil { runErrors = append(runErrors, err) return runErrors } for _, p := range paths { log.Debug("Processing template resource " + p) t, err := NewTemplateResourceFromPath(p, c) if err != nil { runErrors = append(runErrors, err) log.Error(err.Error()) continue } if err := t.process(); err != nil { runErrors = append(runErrors, err) log.Error(err.Error()) continue } log.Debug("Processing of template resource " + p + " complete") } return runErrors }
// sync compares the staged and dest config files and attempts to sync them // if they differ. sync will run a config check command if set before // overwriting the target config file. Finally, sync will run a reload command // if set to have the application or service pick up the changes. // It returns an error if any. func (t *TemplateResource) sync() error { staged := t.StageFile.Name() defer os.Remove(staged) log.Debug("Comparing candidate config to " + t.Dest) ok, err := sameConfig(staged, t.Dest) if err != nil { log.Error(err.Error()) } if config.Noop() { log.Warning("Noop mode enabled " + t.Dest + " will not be modified") return nil } if !ok { log.Info("Target config " + t.Dest + " out of sync") if t.CheckCmd != "" { if err := t.check(); err != nil { return errors.New("Config check failed: " + err.Error()) } } log.Debug("Overwriting target config " + t.Dest) if err := os.Rename(staged, t.Dest); err != nil { return err } if t.ReloadCmd != "" { if err := t.reload(); err != nil { return err } } log.Info("Target config " + t.Dest + " has been updated") } else { log.Info("Target config " + t.Dest + " in sync") } return nil }
// LoadConfig initializes the confd configuration by first setting defaults, // then overriding setting from the confd config file, and finally overriding // settings from flags set on the command line. // It returns an error if any. func LoadConfig(path string) error { setDefaults() if path == "" { log.Warning("Skipping confd config file.") } else { log.Debug("Loading " + path) configBytes, err := ioutil.ReadFile(path) if err != nil { return err } _, err = toml.Decode(string(configBytes), &config) if err != nil { return err } } processFlags() if !isValidateEtcdScheme(config.Confd.EtcdScheme) { return errors.New("Invalid etcd scheme: " + config.Confd.EtcdScheme) } err := setEtcdHosts() if err != nil { return err } return nil }
// sync compares the staged and dest config files and attempts to sync them // if they differ. sync will run a config check command if set before // overwriting the target config file. Finally, sync will run a reload command // if set to have the application or service pick up the changes. // It returns an error if any. func (t *TemplateResource) sync() error { staged := t.StageFile.Name() if t.keepStageFile { log.Info("Keeping staged file: " + staged) } else { defer os.Remove(staged) } log.Debug("Comparing candidate config to " + t.Dest) ok, err := sameConfig(staged, t.Dest) if err != nil { log.Error(err.Error()) } if t.noop { log.Warning("Noop mode enabled. " + t.Dest + " will not be modified") return nil } if !ok { log.Info("Target config " + t.Dest + " out of sync") if t.CheckCmd != "" { if err := t.check(); err != nil { return errors.New("Config check failed: " + err.Error()) } } log.Debug("Overwriting target config " + t.Dest) err := os.Rename(staged, t.Dest) if err != nil { if strings.Contains(err.Error(), "device or resource busy") { log.Debug("Rename failed - target is likely a mount. Trying to write instead") // try to open the file and write to it var contents []byte var rerr error contents, rerr = ioutil.ReadFile(staged) if rerr != nil { return rerr } err := ioutil.WriteFile(t.Dest, contents, t.FileMode) // make sure owner and group match the temp file, in case the file was created with WriteFile os.Chown(t.Dest, t.Uid, t.Gid) if err != nil { return err } } else { return err } } if t.ReloadCmd != "" { if err := t.reload(); err != nil { return err } } log.Info("Target config " + t.Dest + " has been updated") } else { log.Debug("Target config " + t.Dest + " in sync") } return nil }
// recursively walks on all the values of a specific key and set them in the variables map func flatten(key string, value interface{}, vars map[string]string) { switch value.(type) { case string: log.Debug("setting key %s to: %s", key, value) vars[key] = value.(string) case map[string]interface{}: inner := value.(map[string]interface{}) for innerKey, innerValue := range inner { innerKey = path.Join(key, "/", innerKey) flatten(innerKey, innerValue, vars) } default: // we don't know how to handle non string or maps of strings log.Warning("type of '%s' is not supported (%T)", key, value) } }
func getTemplateResources(config Config) ([]*TemplateResource, error) { var lastError error templates := make([]*TemplateResource, 0) log.Debug("Loading template resources from confdir " + config.ConfDir) if !isFileExist(config.ConfDir) { log.Warning(fmt.Sprintf("Cannot load template resources: confdir '%s' does not exist", config.ConfDir)) return nil, nil } paths, err := recursiveFindFiles(config.ConfigDir, "*toml") if err != nil { return nil, err } for _, p := range paths { t, err := NewTemplateResource(p, config) if err != nil { lastError = err continue } templates = append(templates, t) } // get dynamic TemplateResources log.Info("parse dynamic keys") result, err := config.StoreClient.GetValues([]string{"_confd"}) if err == nil { for k, v := range result { log.Info("dynamic key: " + k + " / " + v) t, err := NewTemplateResource(config.ConfigDir+"/"+v, config) if err != nil { lastError = err continue } split := strings.Split(k, "/") key := "/" + split[len(split)-1] t.Dest = strings.Replace(t.Dest, "{{.token}}", key, 1) t.ReloadCmd = strings.Replace(t.ReloadCmd, "{{.token}}", key, 1) t.Prefix = key t.prefix = key templates = append(templates, t) } } return templates, lastError }
func getTemplateResources(config Config) ([]*TemplateResource, error) { var lastError error templates := make([]*TemplateResource, 0) log.Debug("Loading template resources from confdir " + config.ConfDir) if !isFileExist(config.ConfDir) { log.Warning(fmt.Sprintf("Cannot load template resources: confdir '%s' does not exist", config.ConfDir)) return nil, nil } paths, err := filepath.Glob(filepath.Join(config.ConfigDir, "*toml")) if err != nil { return nil, err } for _, p := range paths { t, err := NewTemplateResource(p, config) if err != nil { lastError = err continue } templates = append(templates, t) } return templates, lastError }
// initConfig initializes the confd configuration by first setting defaults, // then overriding setting from the confd config file, and finally overriding // settings from flags set on the command line. // It returns an error if any. func initConfig() error { if configFile == "" { if _, err := os.Stat(defaultConfigFile); !os.IsNotExist(err) { configFile = defaultConfigFile } } // Set defaults. config = Config{ Backend: "etcd", ConfDir: "/etc/confd", Interval: 600, Prefix: "/", Scheme: "http", } // Update config from the TOML configuration file. if configFile == "" { log.Warning("Skipping confd config file.") } else { log.Debug("Loading " + configFile) configBytes, err := ioutil.ReadFile(configFile) if err != nil { return err } _, err = toml.Decode(string(configBytes), &config) if err != nil { return err } } // Update config from commandline flags. processFlags() // Configure logging. log.SetQuiet(config.Quiet) log.SetVerbose(config.Verbose) log.SetDebug(config.Debug) // Update BackendNodes from SRV records. if config.Backend != "env" && config.SRVDomain != "" { log.Info("SRV domain set to " + config.SRVDomain) srvNodes, err := getBackendNodesFromSRV(config.Backend, config.SRVDomain, config.Scheme) if err != nil { return errors.New("Cannot get nodes from SRV records " + err.Error()) } config.BackendNodes = srvNodes } if len(config.BackendNodes) == 0 { switch config.Backend { case "consul": config.BackendNodes = []string{"127.0.0.1:8500"} case "etcd": peerstr := os.Getenv("ETCDCTL_PEERS") if len(peerstr) > 0 { config.BackendNodes = strings.Split(peerstr, ",") } else { config.BackendNodes = []string{"http://127.0.0.1:4001"} } case "redis": config.BackendNodes = []string{"127.0.0.1:6379"} } } // Initialize the storage client log.Notice("Backend set to " + config.Backend) if config.Watch { unsupportedBackends := map[string]bool{ "zookeeper": true, "redis": true, } if unsupportedBackends[config.Backend] { log.Notice(fmt.Sprintf("Watch is not supported for backend %s. Exiting...", config.Backend)) os.Exit(1) } } backendsConfig = backends.Config{ Backend: config.Backend, ClientCaKeys: config.ClientCaKeys, ClientCert: config.ClientCert, ClientKey: config.ClientKey, BackendNodes: config.BackendNodes, Scheme: config.Scheme, } // Template configuration. templateConfig = template.Config{ ConfDir: config.ConfDir, ConfigDir: filepath.Join(config.ConfDir, "conf.d"), KeepStageFile: keepStageFile, Noop: config.Noop, Prefix: config.Prefix, TemplateDir: filepath.Join(config.ConfDir, "templates"), } return nil }