// GetValues queries etcd for keys prefixed by prefix. func (c *Client) GetValues(keys []string) (map[string]string, error) { vars := make(map[string]string) for _, key := range keys { log.Debug("getting %s from vault", key) resp, err := c.client.Logical().Read(key) if err != nil { log.Debug("there was an error extracting %s", key) return nil, err } if resp == nil || resp.Data == nil { continue } // if the key has only one string value // treat it as a string and not a map of values if val, ok := isKV(resp.Data); ok { vars[key] = val } else { // save the json encoded response // and flatten it to allow usage of gets & getvs js, _ := json.Marshal(resp.Data) vars[key] = string(js) flatten(key, resp.Data, vars) } } return vars, nil }
// createStageFile stages the src configuration file by processing the src // template and setting the desired owner, group, and mode. It also sets the // StageFile for the template resource. // It returns an error if any. func (t *TemplateResource) createStageFile() error { log.Debug("Using source template " + t.Src) if !isFileExist(t.Src) { return errors.New("Missing template: " + t.Src) } log.Debug("Compiling source template " + t.Src) tmpl, err := template.New(path.Base(t.Src)).Funcs(t.funcMap).ParseFiles(t.Src) if err != nil { return fmt.Errorf("Unable to process template %s, %s", t.Src, err) } // create TempFile in Dest directory to avoid cross-filesystem issues temp, err := ioutil.TempFile(filepath.Dir(t.Dest), "."+filepath.Base(t.Dest)) if err != nil { return err } if err = tmpl.Execute(temp, nil); err != nil { temp.Close() os.Remove(temp.Name()) return err } defer temp.Close() // Set the owner, group, and mode on the stage file now to make it easier to // compare against the destination configuration file later. os.Chmod(temp.Name(), t.FileMode) os.Chown(temp.Name(), t.Uid, t.Gid) t.StageFile = temp 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 }
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 }
// 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 }
// createStageFile stages the src configuration file by processing the src // template and setting the desired owner, group, and mode. It also sets the // StageFile for the template resource. // It returns an error if any. func (t *TemplateResource) createStageFile() error { t.Src = filepath.Join(config.TemplateDir(), t.Src) log.Debug("Using source template " + t.Src) if !isFileExist(t.Src) { return errors.New("Missing template: " + t.Src) } // create TempFile in Dest directory to avoid cross-filesystem issues temp, err := ioutil.TempFile(filepath.Dir(t.Dest), "."+filepath.Base(t.Dest)) if err != nil { os.Remove(temp.Name()) return err } defer temp.Close() log.Debug("Compiling source template " + t.Src) tplFuncMap := make(template.FuncMap) tplFuncMap["Base"] = path.Base tplFuncMap["GetDir"] = t.Dirs.Get tmpl := template.Must(template.New(path.Base(t.Src)).Funcs(tplFuncMap).ParseFiles(t.Src)) if err = tmpl.Execute(temp, t.Vars); err != nil { return err } // Set the owner, group, and mode on the stage file now to make it easier to // compare against the destination configuration file later. os.Chmod(temp.Name(), t.FileMode) os.Chown(temp.Name(), t.Uid, t.Gid) t.StageFile = temp 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 }
// setVars sets the Vars for template resource. func (t *TemplateResource) setVars() error { var err error log.Debug("Retrieving keys from etcd") log.Debug("Key prefix set to " + config.Prefix()) t.Vars, err = etcdutil.GetValues(t.etcdClient, config.Prefix(), t.Keys) if err != nil { return err } return nil }
// 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 { return err } log.Debug(fmt.Sprintf("%q", string(output))) return nil }
// setVars sets the Vars for template resource. func (t *TemplateResource) setVars() error { var err error log.Debug("Retrieving keys from store") log.Debug("Key prefix set to " + config.Prefix()) vars, err := t.storeClient.GetValues(appendPrefix(config.Prefix(), t.Keys)) if err != nil { return err } t.Vars = cleanKeys(vars) return nil }
// setVars sets the Vars for template resource. func (t *TemplateResource) setVars() error { var err error log.Debug("Retrieving keys from store") log.Debug("Key prefix set to " + t.prefix) result, err := t.storeClient.GetValues(appendPrefix(t.prefix, t.Keys)) if err != nil { return err } t.store.Purge() for k, v := range result { t.store.Set(filepath.Join("/", strings.TrimPrefix(k, t.prefix)), v) } return nil }
// NewDynamoDBClient returns an *dynamodb.Client with a connection to the region // configured via the AWS_REGION environment variable. // It returns an error if the connection cannot be made or the table does not exist. func NewDynamoDBClient(table string) (*Client, error) { creds := credentials.NewChainCredentials( []credentials.Provider{ &credentials.EnvProvider{}, &credentials.EC2RoleProvider{}, }) _, err := creds.Get() if err != nil { return nil, err } var c *aws.Config if os.Getenv("DYNAMODB_LOCAL") != "" { log.Debug("DYNAMODB_LOCAL is set") c = &aws.Config{Endpoint: "http://localhost:8000"} } else { c = nil } d := dynamodb.New(c) // Check if the table exists _, err = d.DescribeTable(&dynamodb.DescribeTableInput{TableName: &table}) if err != nil { return nil, err } return &Client{d, table}, 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 }
// Iterate through `machines`, trying to connect to each in turn. // Returns the first successful connection or the last error encountered. // Assumes that `machines` is non-empty. func tryConnect(machines []string, password string) (redis.Conn, error) { var err error for _, address := range machines { var conn redis.Conn network := "tcp" if _, err = os.Stat(address); err == nil { network = "unix" } log.Debug(fmt.Sprintf("Trying to connect to redis node %s", address)) dialops := []redis.DialOption{ redis.DialConnectTimeout(time.Second), redis.DialReadTimeout(time.Second), redis.DialWriteTimeout(time.Second), } if password != "" { dialops = append(dialops, redis.DialPassword(password)) } conn, err = redis.Dial(network, address, dialops...) if err != nil { continue } return conn, nil } return nil, err }
// NewDynamoDBClient returns an *dynamodb.Client with a connection to the region // configured via the AWS_REGION environment variable. // It returns an error if the connection cannot be made or the table does not exist. func NewDynamoDBClient(table string) (*Client, error) { var c *aws.Config if os.Getenv("DYNAMODB_LOCAL") != "" { log.Debug("DYNAMODB_LOCAL is set") endpoint := "http://localhost:8000" c = &aws.Config{ Endpoint: &endpoint, } } else { c = nil } session := session.New(c) // Fail early, if no credentials can be found _, err := session.Config.Credentials.Get() if err != nil { return nil, err } d := dynamodb.New(session) // Check if the table exists _, err = d.DescribeTable(&dynamodb.DescribeTableInput{TableName: &table}) if err != nil { return nil, err } return &Client{d, table}, nil }
// NewTemplateResource creates a TemplateResource. func NewTemplateResource(path string, config Config) (*TemplateResource, error) { if config.StoreClient == nil { return nil, errors.New("A valid StoreClient is required.") } var tc *TemplateResourceConfig log.Debug("Loading template resource from " + path) _, err := toml.DecodeFile(path, &tc) if err != nil { return nil, fmt.Errorf("Cannot process template resource %s - %s", path, err.Error()) } tr := tc.TemplateResource tr.expandEnv() tr.keepStageFile = config.KeepStageFile tr.noop = config.Noop tr.storeClient = config.StoreClient tr.funcMap = newFuncMap() tr.store = memkv.New() addFuncs(tr.funcMap, tr.store.FuncMap) tr.prefix = filepath.Join("/", config.Prefix, tr.Prefix) if tr.Src == "" { return nil, ErrEmptySrc } tr.Src = filepath.Join(config.TemplateDir, tr.Src) return &tr, nil }
// 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) if err := c.Run(); err != nil { return err } return nil }
func main() { // Most flags are defined in the confd/config package which allows us to // override configuration settings from the command line. Parse the flags now // to make them active. flag.Parse() if printVersion { fmt.Printf("confd %s\n", Version) os.Exit(0) } if configFile == "" { if IsFileExist(defaultConfigFile) { configFile = defaultConfigFile } } // Initialize the global configuration. log.Debug("Loading confd configuration") if err := config.LoadConfig(configFile); err != nil { log.Fatal(err.Error()) } // Configure logging. While you can enable debug and verbose logging, however // if quiet is set to true then debug and verbose messages will not be printed. log.SetQuiet(config.Quiet()) log.SetVerbose(config.Verbose()) log.SetDebug(config.Debug()) log.Notice("Starting confd") // Create the storage client log.Notice("Backend set to " + config.Backend()) store, err := backends.New(config.Backend()) if err != nil { log.Fatal(err.Error()) } signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) for { runErrors := template.ProcessTemplateResources(store) // If the -onetime flag is passed on the command line we immediately exit // after processing the template config files. if onetime { if len(runErrors) > 0 { os.Exit(1) } os.Exit(0) } select { case c := <-signalChan: log.Info(fmt.Sprintf("captured %v exiting...", c)) os.Exit(0) case <-time.After(time.Duration(config.Interval()) * time.Second): // Continue processing templates. } } }
// 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 { 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.Debug(err.Error()) case s := <-signalChan: log.Info(fmt.Sprintf("Captured %v. Exiting...", s)) close(doneChan) case <-doneChan: os.Exit(0) } } }
// NewTemplateResourceFromPath creates a TemplateResource using a decoded file path // and the supplied EtcdClient as input. // It returns a TemplateResource and an error if any. func NewTemplateResourceFromPath(path string, c etcdutil.EtcdClient) (*TemplateResource, error) { if c == nil { return nil, errors.New("A valid EtcdClient is required.") } var tc *TemplateResourceConfig log.Debug("Loading template resource from " + path) _, err := toml.DecodeFile(path, &tc) if err != nil { return nil, fmt.Errorf("Cannot process template resource %s - %s", path, err.Error()) } tc.TemplateResource.etcdClient = c return &tc.TemplateResource, nil }
// createStageFile stages the src configuration file by processing the src // template and setting the desired owner, group, and mode. It also sets the // StageFile for the template resource. // It returns an error if any. func (t *TemplateResource) createStageFile() error { t.Src = filepath.Join(config.TemplateDir(), t.Src) log.Debug("Using source template " + t.Src) if !isFileExist(t.Src) { return errors.New("Missing template: " + t.Src) } temp, err := ioutil.TempFile("", "") if err != nil { os.Remove(temp.Name()) return err } log.Debug("Compiling source template " + t.Src) tmpl := template.Must(template.ParseFiles(t.Src)) if err = tmpl.Execute(temp, t.Vars); err != nil { return err } // Set the owner, group, and mode on the stage file now to make it easier to // compare against the destination configuration file later. os.Chmod(temp.Name(), t.FileMode) os.Chown(temp.Name(), t.Uid, t.Gid) t.StageFile = temp 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) } }
// Iterate through `machines`, trying to connect to each in turn. // Returns the first successful connection or the last error encountered. // Assumes that `machines` is non-empty. func tryConnect(machines []string) (redis.Conn, error) { var err error for _, address := range machines { var conn redis.Conn network := "tcp" if _, err = os.Stat(address); err == nil { network = "unix" } log.Debug(fmt.Sprintf("Trying to connect to redis node %s", address)) conn, err = redis.DialTimeout(network, address, time.Second, time.Second, time.Second) if err != nil { continue } return conn, nil } return nil, err }
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 }
// NewTemplateResource creates a TemplateResource. func NewTemplateResource(path string, config Config) (*TemplateResource, error) { if config.StoreClient == nil { return nil, errors.New("A valid StoreClient is required.") } // Set the default uid and gid so we can determine if it was // unset from configuration. tc := &TemplateResourceConfig{TemplateResource{Uid: -1, Gid: -1}} log.Debug("Loading template resource from " + path) _, err := toml.DecodeFile(path, &tc) if err != nil { return nil, fmt.Errorf("Cannot process template resource %s - %s", path, err.Error()) } tr := tc.TemplateResource tr.keepStageFile = config.KeepStageFile tr.noop = config.Noop tr.storeClient = config.StoreClient tr.funcMap = newFuncMap() tr.store = memkv.New() tr.syncOnly = config.SyncOnly addFuncs(tr.funcMap, tr.store.FuncMap) if config.Prefix != "" { tr.Prefix = config.Prefix } tr.Prefix = filepath.Join("/", tr.Prefix) if tr.Src == "" { return nil, ErrEmptySrc } if tr.Uid == -1 { tr.Uid = os.Geteuid() } if tr.Gid == -1 { tr.Gid = os.Getegid() } tr.Src = filepath.Join(config.TemplateDir, tr.Src) return &tr, nil }
// authenticate with the remote client func authenticate(c *vaultapi.Client, authType string, params map[string]string) (err error) { var secret *vaultapi.Secret // handle panics gracefully by creating an error // this would happen when we get a parameter that is missing defer panicToError(&err) switch authType { case "app-id": secret, err = c.Logical().Write("/auth/app-id/login", map[string]interface{}{ "app_id": getParameter("app-id", params), "user_id": getParameter("user-id", params), }) case "github": secret, err = c.Logical().Write("/auth/github/login", map[string]interface{}{ "token": getParameter("token", params), }) case "token": c.SetToken(getParameter("token", params)) secret, err = c.Logical().Read("/auth/token/lookup-self") case "userpass": username, password := getParameter("username", params), getParameter("password", params) secret, err = c.Logical().Write(fmt.Sprintf("/auth/userpass/login/%s", username), map[string]interface{}{ "password": password, }) } if err != nil { return err } // if the token has already been set if c.Token() != "" { return nil } log.Debug("client authenticated with auth backend: %s", authType) // the default place for a token is in the auth section // otherwise, the backend will set the token itself c.SetToken(secret.Auth.ClientToken) return nil }
func main() { // Most flags are defined in the confd/config package which allows us to // override configuration settings from the command line. Parse the flags now // to make them active. flag.Parse() if configFile == "" { if IsFileExist(defaultConfigFile) { configFile = defaultConfigFile } } // Initialize the global configuration. log.Debug("Loading confd configuration") if err := config.LoadConfig(configFile); err != nil { log.Fatal(err.Error()) } // Configure logging. While you can enable debug and verbose logging, however // if quiet is set to true then debug and verbose messages will not be printed. log.SetQuiet(config.Quiet()) log.SetVerbose(config.Verbose()) log.SetDebug(config.Debug()) log.Notice("Starting confd") // Create the etcd client upfront and use it for the life of the process. // The etcdClient is an http.Client and designed to be reused. log.Notice("etcd nodes set to " + strings.Join(config.EtcdNodes(), ", ")) etcdClient, err := etcdutil.NewEtcdClient(config.EtcdNodes(), config.ClientCert(), config.ClientKey(), config.ClientCaKeys()) if err != nil { log.Fatal(err.Error()) } for { runErrors := template.ProcessTemplateResources(etcdClient) // If the -onetime flag is passed on the command line we immediately exit // after processing the template config files. if onetime { if len(runErrors) > 0 { os.Exit(1) } os.Exit(0) } time.Sleep(time.Duration(config.Interval()) * time.Second) } }
func main() { // Most flags are defined in the confd/config package which allows us to // override configuration settings from the command line. Parse the flags now // to make them active. flag.Parse() if configFile == "" { if IsFileExist(defaultConfigFile) { configFile = defaultConfigFile } } // Initialize the global configuration. log.Debug("Loading confd configuration") if err := config.LoadConfig(configFile); err != nil { log.Fatal(err.Error()) } // Configure logging. While you can enable debug and verbose logging, however // if quiet is set to true then debug and verbose messages will not be printed. log.SetQuiet(config.Quiet()) log.SetVerbose(config.Verbose()) log.SetDebug(config.Debug()) log.Notice("Starting confd") // Create the storage client store, err := createStoreClient(config.Backend()) if err != nil { log.Fatal(err.Error()) } for { runErrors := template.ProcessTemplateResources(store) // If the -onetime flag is passed on the command line we immediately exit // after processing the template config files. if onetime { if len(runErrors) > 0 { os.Exit(1) } os.Exit(0) } time.Sleep(time.Duration(config.Interval()) * time.Second) } }
// GetValues queries the environment for keys func (c *Client) GetValues(keys []string) (map[string]string, error) { allEnvVars := os.Environ() envMap := make(map[string]string) for _, e := range allEnvVars { index := strings.Index(e, "=") envMap[e[:index]] = e[index+1:] } vars := make(map[string]string) for _, key := range keys { k := transform(key) for envKey, envValue := range envMap { if strings.HasPrefix(envKey, k) { vars[clean(envKey)] = envValue } } } log.Debug(fmt.Sprintf("Key Map: %#v", vars)) return vars, nil }