// 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 }
func treeWalk(root string, val interface{}, vars map[string]string) error { switch val.(type) { case map[string]interface{}: for k := range val.(map[string]interface{}) { treeWalk(strings.Join([]string{root, k}, "/"), val.(map[string]interface{})[k], vars) } case []interface{}: for i, item := range val.([]interface{}) { idx := strconv.Itoa(i) if i, isMap := item.(map[string]interface{}); isMap { if name, exists := i["name"]; exists { idx = name.(string) } } treeWalk(strings.Join([]string{root, idx}, "/"), item, vars) } case bool: vars[root] = strconv.FormatBool(val.(bool)) case string: vars[root] = val.(string) case float64: vars[root] = strconv.FormatFloat(val.(float64), 'f', -1, 64) case nil: vars[root] = "null" default: log.Error("Unknown type: " + reflect.TypeOf(val).Name()) } 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() 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 }
// 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) err, ok := sameConfig(staged, t.Dest) if err != nil { log.Error(err.Error()) } if !ok { log.Info(t.Dest + " not in sync") if t.CheckCmd != "" { if err := t.check(); err != nil { return errors.New("Config check failed: " + err.Error()) } } os.Rename(staged, t.Dest) if t.ReloadCmd != "" { if err := t.reload(); err != nil { return err } } } else { log.Info(t.Dest + " in sync") } 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 }
func process(ts []*TemplateResource) error { var lastErr error for _, t := range ts { if err := t.process(); err != nil { log.Error(err.Error()) lastErr = err } } return lastErr }
// reload executes the reload command. // It returns nil if the reload command returns 0. func (t *TemplateResource) reload() error { log.Debug("Running " + t.ReloadCmd) c := exec.Command("/bin/sh", "-c", t.ReloadCmd) output, err := c.CombinedOutput() if err != nil { log.Error(fmt.Sprintf("%q", string(output))) return err } log.Debug(fmt.Sprintf("%q", string(output))) return nil }
func main() { flag.Parse() if printVersion { fmt.Printf("confd %s\n", Version) os.Exit(0) } if err := initConfig(); err != nil { log.Fatal(err.Error()) } log.Info("Starting confd") storeClient, err := backends.New(backendsConfig) if err != nil { log.Fatal(err.Error()) } templateConfig.StoreClient = storeClient if onetime { if err := template.Process(templateConfig); err != nil { os.Exit(1) } os.Exit(0) } stopChan := make(chan bool) doneChan := make(chan bool) errChan := make(chan error, 10) var processor template.Processor switch { case config.Watch: processor = template.WatchProcessor(templateConfig, stopChan, doneChan, errChan) default: processor = template.IntervalProcessor(templateConfig, stopChan, doneChan, errChan, config.Interval) } go processor.Process() signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) for { select { case err := <-errChan: log.Error(err.Error()) case s := <-signalChan: log.Info(fmt.Sprintf("Captured %v. Exiting...", s)) close(doneChan) case <-doneChan: os.Exit(0) } } }
// setEtcdHosts. func setEtcdHosts() error { scheme := config.Confd.EtcdScheme hosts := make([]string, 0) // If a domain name is given then lookup the etcd SRV record, and override // all other etcd node settings. if config.Confd.SRVDomain != "" { log.Info("SRV domain set to " + config.Confd.SRVDomain) etcdHosts, err := getEtcdHostsFromSRV(config.Confd.SRVDomain) if err != nil { return errors.New("Cannot get etcd hosts from SRV records " + err.Error()) } for _, h := range etcdHosts { uri := formatEtcdHostURL(scheme, h.Hostname, strconv.FormatUint(uint64(h.Port), 10)) hosts = append(hosts, uri) } config.Confd.EtcdNodes = hosts return nil } // No domain name was given, so just process the etcd node list. // An etcdNode can be a URL, http://etcd.example.com:4001, or a host, etcd.example.com:4001. for _, node := range config.Confd.EtcdNodes { etcdURL, err := url.Parse(node) if err != nil { log.Error(err.Error()) return err } if etcdURL.Scheme != "" && etcdURL.Host != "" { if !isValidateEtcdScheme(etcdURL.Scheme) { return errors.New("The etcd node list contains an invalid URL: " + node) } host, port, err := net.SplitHostPort(etcdURL.Host) if err != nil { return err } hosts = append(hosts, formatEtcdHostURL(etcdURL.Scheme, host, port)) continue } // At this point node is not an etcd URL, i.e. http://etcd.example.com:4001, // but a host:port string, i.e. etcd.example.com:4001 host, port, err := net.SplitHostPort(node) if err != nil { return err } hosts = append(hosts, formatEtcdHostURL(scheme, host, port)) } config.Confd.EtcdNodes = hosts return nil }
func (c *Client) GetValues(keys []string) (map[string]string, error) { vars := make(map[string]string) for _, v := range keys { bucketsKey := strings.Split(strings.TrimPrefix(v, "/"), "/") buckets := strings.Split(bucketsKey[0], ",") key := bucketsKey[1] dynamicBuckets, err := c.getDynamicBuckets(buckets) if err != nil { return vars, err } for _, dynamicBucket := range dynamicBuckets { val := dynamicBucket.GetKeys()[key] if val == nil { continue } valType := reflect.TypeOf(val).Kind() if valType == reflect.Slice { data, err := ffjson.Marshal(val) if err != nil { log.Error("Failed decoding from JSON") } else { vars[key] = string(data[:]) } } else { switch val.(type) { case int, int64: vars[key] = strconv.FormatInt(val.(int64), 64) case string: vars[key] = val.(string) case bool: vars[key] = strconv.FormatBool(val.(bool)) case float32, float64: vars[key] = strconv.FormatFloat(val.(float64), 'f', -1, 64) } } } } return vars, nil }
// check executes the check command to validate the staged config file. The // command is modified so that any references to src template are substituted // with a string representing the full path of the staged file. This allows the // check to be run on the staged file before overwriting the destination config // file. // It returns nil if the check command returns 0 and there are no other errors. func (t *TemplateResource) check() error { var cmdBuffer bytes.Buffer data := make(map[string]string) data["src"] = t.StageFile.Name() tmpl, err := template.New("checkcmd").Parse(t.CheckCmd) if err != nil { return err } if err := tmpl.Execute(&cmdBuffer, data); err != nil { return err } log.Debug("Running " + cmdBuffer.String()) c := exec.Command("/bin/sh", "-c", cmdBuffer.String()) output, err := c.CombinedOutput() if err != nil { log.Error(fmt.Sprintf("%q", string(output))) return err } log.Debug(fmt.Sprintf("%q", string(output))) return nil }
func main() { log.Info("Starting confd") // All flags are defined in the confd/config package which allow us to // override configuration settings from the cli. Parse the flags now to // make them active. flag.Parse() if err := InitConfig(); err != nil { log.Fatal(err.Error()) } for { if err := ProcessTemplateResources(); err != nil { log.Error(err.Error()) } // If the -onetime flag is passed on the command line we immediately exit // after processing the template config files. if Onetime() { break } // By default we poll etcd every 30 seconds time.Sleep(time.Duration(Interval()) * time.Second) } }
// Retrieves a connected redis client from the client wrapper. // Existing connections will be tested with a PING command before being returned. Tries to reconnect once if necessary. // Returns the established redis connection or the error encountered. func (c *Client) connectedClient() (redis.Conn, error) { if c.client != nil { log.Debug("Testing existing redis connection.") resp, err := c.client.Do("PING") if (err != nil && err == redis.ErrNil) || resp != "PONG" { log.Error(fmt.Sprintf("Existing redis connection no longer usable. "+ "Will try to re-establish. Error: %s", err.Error())) c.client = nil } } // Existing client could have been deleted by previous block if c.client == nil { var err error c.client, err = tryConnect(c.machines) if err != nil { return nil, err } } return c.client, nil }
// GetValues retrieves the private and public ips and DNS names // of instances with HealthStatus == "Healthy" and // LifecycleState == "InService" for the Auto Scaling Group in c.asg func (c *Client) GetValues(keys []string) (map[string]string, error) { vars := make(map[string]string) asgResponse, err := c.asgClient.DescribeAutoScalingGroups( &autoscaling.DescribeAutoScalingGroupsInput{ AutoScalingGroupNames: []*string{&c.asg}, }, ) if err != nil || len(asgResponse.AutoScalingGroups) == 0 { log.Info("Can't find Auto Scaling Group with name '" + c.asg + "'") return nil, err } asg := asgResponse.AutoScalingGroups[0] instance_ids := []*string{} for _, instance := range asg.Instances { if *instance.HealthStatus == "Healthy" && *instance.LifecycleState == "InService" { instance_ids = append(instance_ids, instance.InstanceId) } } if len(instance_ids) == 0 { log.Info("Can't find any instances in Auto Scaling Group with name '" + c.asg + "'") return nil, errors.New("Can't find any instances in Auto Scaling Group with name '" + c.asg + "'") } ec2Response, err := c.ec2Client.DescribeInstances( &ec2.DescribeInstancesInput{ InstanceIds: instance_ids, }, ) if err != nil { log.Error("Failed describing instances") return nil, err } instances := map[string]map[string]string{} for _, reservation := range ec2Response.Reservations { for _, instance := range reservation.Instances { if instance.InstanceId != nil { // Do not include instance if it doesn't have a PrivateIpAddress if instance.PrivateIpAddress != nil { instances[*instance.InstanceId] = map[string]string{ "PrivateIpAddress": *instance.PrivateIpAddress, } if instance.PrivateDnsName != nil { instances[*instance.InstanceId]["PrivateDnsName"] = *instance.PrivateDnsName } if instance.PublicIpAddress != nil { instances[*instance.InstanceId]["PublicIpAddress"] = *instance.PublicIpAddress } if instance.PublicDnsName != nil { instances[*instance.InstanceId]["PublicDnsName"] = *instance.PublicDnsName } } } } } var instancesKeys []string for k := range instances { instancesKeys = append(instancesKeys, k) } sort.Strings(instancesKeys) var i int = 0 for _, k := range instancesKeys { iStr := strconv.Itoa(i) vars["privateIps/"+iStr] = instances[k]["PrivateIpAddress"] vars["privateDnsNames/"+iStr] = instances[k]["PrivateDnsName"] if _, present := instances[k]["PublicIpAddress"]; present { vars["publicIps/"+iStr] = instances[k]["PublicIpAddress"] } if _, present := instances[k]["PublicDnsName"]; present { vars["publicDnsNames/"+iStr] = instances[k]["PublicDnsName"] } i++ } return vars, nil }