diff --git a/.gitignore b/.gitignore index 82d82acd..1d159f87 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /.coveralls-stamp /release/ /profile.out +/cmd/envetcd/envetcd diff --git a/Makefile b/Makefile index e1366f17..3e545e96 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,7 @@ release-linux-amd64: $(EXECUTABLE) $(EXECUTABLE)-linux-amd64 @mkdir -p release @tar czf release/$(EXECUTABLE)-$(VERSION)-linux-amd64.tgz $(EXECUTABLE)-$(VERSION)-linux-amd64 @rm -rf $(EXECUTABLE)-$(VERSION)-linux-amd64 + @cp $(EXECUTABLE)-linux-amd64 release/ release-darwin-amd64: $(EXECUTABLE) $(EXECUTABLE)-darwin-amd64 $(eval VERSION=$(shell ./$(EXECUTABLE) -v | awk '{print $$3}')) @@ -79,6 +80,7 @@ release-darwin-amd64: $(EXECUTABLE) $(EXECUTABLE)-darwin-amd64 @mkdir -p release @tar czf release/$(EXECUTABLE)-$(VERSION)-darwin-amd64.tgz $(EXECUTABLE)-$(VERSION)-darwin-amd64 @rm -rf $(EXECUTABLE)-$(VERSION)-darwin-amd64 + @cp $(EXECUTABLE)-darwin-amd64 release/ clean: @rm -rf \ diff --git a/cmd/envetcd/cli.go b/cmd/envetcd/cli.go index 12b61125..7c6ab342 100644 --- a/cmd/envetcd/cli.go +++ b/cmd/envetcd/cli.go @@ -8,7 +8,6 @@ import ( "github.com/codegangsta/cli" "github.com/zvelo/envetcd" - "github.com/zvelo/zvelo-services/util" ) // Exit codes are int values that represent an exit code for a particular error. @@ -24,28 +23,6 @@ const ( exitCodeEnvEtcdError ) -func setup(c *cli.Context) error { - util.InitLogger(c.GlobalString("log-level")) - - config = configT{ - EnvEtcd: &envetcd.Config{ - Etcd: util.NewEtcdConfig(c), - Hostname: c.GlobalString("hostname"), - System: c.GlobalString("system"), - Service: c.GlobalString("service"), - Prefix: c.GlobalString("prefix"), - Sanitize: !c.GlobalBool("no-sanitize"), - Upcase: !c.GlobalBool("no-upcase"), - UseDefaultGateway: c.GlobalBool("use-default-gateway"), - }, - Output: c.String("output"), - WriteEnv: c.GlobalString("write-env"), - CleanEnv: c.GlobalBool("clean-env"), - } - - return nil -} - // Run accepts a slice of arguments and returns an int representing the exit // status from the command. func run(c *cli.Context) { diff --git a/cmd/envetcd/main.go b/cmd/envetcd/main.go index d11d2a90..3937c959 100644 --- a/cmd/envetcd/main.go +++ b/cmd/envetcd/main.go @@ -10,7 +10,7 @@ import ( const ( name = "envetcd" - version = "0.2.2" + version = "0.3.6" ) type configT struct { @@ -21,8 +21,9 @@ type configT struct { } var ( - app = cli.NewApp() - config configT + app = cli.NewApp() + config configT + templates = cli.StringSlice{} ) func init() { @@ -67,6 +68,15 @@ func init() { EnvVar: "ENVETCD_OUTPUT", Usage: "write stdout from the command to this file", }, + cli.StringSliceFlag{ + Name: "templates, t", + EnvVar: "ENVETCD_TEMPLATES", + Usage: "replace values in this template file using those pulled from etcd," + + "filename should end in '.tmpl'," + + "the substituted file will be written without the '.tmpl' suffix," + + "may be supplied multiple times", + Value: &templates, + }, cli.BoolFlag{ Name: "clean-env, c", EnvVar: "ENVETCD_CLEAN_ENV", @@ -82,16 +92,33 @@ func init() { EnvVar: "ENVETCD_NO_UPCASE", Usage: "don't convert all environment keys to uppercase", }, - cli.BoolFlag{ - Name: "use-default-gateway, d", - EnvVar: "ENVETCD_USE_DEFAULT_GATEWAY", - Usage: "expose the default gateway as $ENVETCD_DEFAULT_GATEWAY", - }, }...) app.Before = setup app.Action = run } +func setup(c *cli.Context) error { + util.InitLogger(c.GlobalString("log-level")) + + config = configT{ + EnvEtcd: &envetcd.Config{ + Etcd: util.NewEtcdConfig(c), + Hostname: c.GlobalString("hostname"), + System: c.GlobalString("system"), + Service: c.GlobalString("service"), + Prefix: c.GlobalString("prefix"), + Sanitize: !c.GlobalBool("no-sanitize"), + Upcase: !c.GlobalBool("no-upcase"), + TemplateFiles: c.StringSlice("templates"), + }, + Output: c.String("output"), + WriteEnv: c.GlobalString("write-env"), + CleanEnv: c.GlobalBool("clean-env"), + } + + return nil +} + func main() { app.Run(os.Args) } diff --git a/envetcd.go b/envetcd.go index 8c95c827..5cccf410 100644 --- a/envetcd.go +++ b/envetcd.go @@ -6,6 +6,7 @@ import ( "log" "net" "os" + "path/filepath" "regexp" "sort" "strconv" @@ -24,7 +25,7 @@ var invalidRegexp = regexp.MustCompile(`[^a-zA-Z0-9_]`) // Config contains all of the parameters needed to run GetKeyPairs type Config struct { - Etcd *util.EtcdConfig + Etcd *util.EtcdConfig Sanitize bool Upcase bool UseDefaultGateway bool @@ -32,6 +33,7 @@ type Config struct { System string Service string Hostname string + TemplateFiles []string } var ( @@ -57,16 +59,66 @@ func init() { } } +func getEnvSlice(key string) []string { + ret := []string{} + + vals := os.Getenv(key) + if len(vals) == 0 { + return ret + } + + for _, val := range strings.Split(vals, ",") { + val = strings.TrimSpace(val) + if len(val) > 0 { + ret = append(ret, val) + } + } + + return ret +} + +func getEnvBool(key string, dflt bool) bool { + val := os.Getenv(key) + if len(val) == 0 { + return dflt + } + + ret, err := strconv.ParseBool(val) + if err != nil { + log.Printf("[WARN] error parsing environment bool ($%s): %s", key, err) + return dflt + } + + return ret +} + +func getEnvDefault(key, dflt string) string { + ret := os.Getenv(key) + if len(ret) == 0 { + return dflt + } + return ret +} + +func initLogger() { + logLevel := "WARN" + if val := os.Getenv("LOG_LEVEL"); len(val) > 0 { + logLevel = val + } + + util.InitLogger(logLevel) +} + // Set modifies the current environment with variables retrieved from etcd. Set // will not overwrite existing variables. // On linux systems, the default gateway will be automatically used as the etcd // endpoint. -// If $ETCD_ENDPOINT is set, it will override the default gateway. -// $ETCD_ENDPOINT should look like "http://127.0.0.1:4001". +// If $ETCD_PEERS is set, it will override the default gateway. +// $ETCD_PEERS should look like "http://127.0.0.1:4001". // service should be set by the application calling Set and not derived from // an environment variable. // Set will also use some other environment variables if they exist. -// $ETCD_PREFIX defaults to "/config" +// $ENVETCD_PREFIX defaults to "/config" // $HOSTNAME will be honored if it is set. // An error is returned only if there was an actual error. Inability to // determine the etcd endpoint as tolerated and not considered an error. In this @@ -79,64 +131,49 @@ func Set(service string) error { return nil } setRun = true - logLevel := os.Getenv("LOG_LEVEL") - if logLevel == "" { - logLevel = "WARN" - } - util.InitLogger(logLevel) + initLogger() - etcdEndpoint := os.Getenv("ETCD_ENDPOINT") + useDefaultGateway := getEnvBool("ETCD_USE_DEFAULT_GATEWAY", true) - useSync := true - if len(os.Getenv("ETCD_NO_SYNC")) > 0 { - useSync = false - } - useDefaultGateway := true - if len(os.Getenv("ENVETCD_USE_DEFAULT_GATEWAY")) > 0 { - if val, err := strconv.ParseBool(os.Getenv("ENVETCD_USE_DEFAULT_GATEWAY")); err != nil { - log.Printf("[INFO] envetcd.Set could not parse $ENVETCD_USE_DEFAULT_GATEWAY, defaulting to true: %v\n", err) - } else { - useDefaultGateway = val - } - } + peers := getEnvSlice("ETCD_PEERS") - if gatewayIP != nil && useDefaultGateway && len(etcdEndpoint) == 0 { - etcdEndpoint = fmt.Sprintf("http://%s:4001", gatewayIP.String()) + if gatewayIP != nil && useDefaultGateway && len(peers) == 0 { + peers = []string{fmt.Sprintf("http://%s:4001", gatewayIP.String())} + } else { + useDefaultGateway = false } - if len(etcdEndpoint) == 0 { + if len(peers) == 0 { log.Println("[INFO] envetcd.Set returned after it could not determine the etcd endpoint") return nil } config := &Config{ Etcd: &util.EtcdConfig{ - Peers: []string{etcdEndpoint}, - Sync: useSync, + Peers: peers, + Sync: !getEnvBool("ETCD_NO_SYNC", false), + UseDefaultGateway: useDefaultGateway, }, - Sanitize: true, - Upcase: true, - UseDefaultGateway: useDefaultGateway, - Prefix: os.Getenv("ETCD_PREFIX"), - Service: service, - Hostname: os.Getenv("HOSTNAME"), + Sanitize: true, + Upcase: true, + Prefix: getEnvDefault("ENVETCD_PREFIX", "/config"), + Service: service, + Hostname: os.Getenv("HOSTNAME"), + TemplateFiles: getEnvSlice("ENVETCD_TEMPLATES"), } if len(config.Etcd.Peers[0]) == 0 { config.Etcd.Peers[0] = "http://127.0.0.1:4001" } - if len(config.Prefix) == 0 { - config.Prefix = "/config" - } - keyPairs, err := GetKeyPairs(config) if err != nil { return err } - log.Printf("[DEBUG] envetcd: %v => %v\n", "ETCD_ENDPOINT", etcdEndpoint) - keyPairs["ETCD_ENDPOINT"] = etcdEndpoint + etcdPeers := strings.Join(peers, ", ") + log.Printf("[DEBUG] envetcd: %v => %v\n", "ETCD_PEERS", etcdPeers) + keyPairs["ETCD_PEERS"] = etcdPeers for key, value := range keyPairs { if len(os.Getenv(key)) == 0 { @@ -147,6 +184,51 @@ func Set(service string) error { return nil } +func processTemplates(keyPairs KeyPairs, tplFiles []string) { + const ext = ".tmpl" + + data := map[string]interface{}{} + arrays := map[string][]string{} + for key, value := range keyPairs { + data[key] = value + arrays[key] = []string{} + for _, val := range strings.Split(value, ",") { + val = strings.TrimSpace(val) + if len(val) > 0 { + arrays[key] = append(arrays[key], val) + } + } + } + data["ARRAY"] = arrays + + for _, tplFile := range tplFiles { + if filepath.Ext(tplFile) != ext { + tplFile += ext + } + + tpl, err := template.ParseFiles(tplFile) + if err != nil { + log.Printf("[WARN] error parsing template (%s): %s", tplFile, err) + continue + } + + fName := tplFile[0 : len(tplFile)-len(ext)] + f, err := os.Create(fName) + if err != nil { + log.Printf("[WARN] error creating file (%s): %s", fName, err) + continue + } + defer f.Close() + + if err := tpl.Execute(f, data); err != nil { + log.Printf("[WARN] error writing file (%s): %s", fName, err) + continue + } + + log.Printf("[INFO] wrote file %s", fName) + } +} + // GetKeyPairs takes a given config and client, and returns all key pairs func GetKeyPairs(config *Config) (KeyPairs, error) { const noSort = false @@ -205,8 +287,8 @@ func GetKeyPairs(config *Config) (KeyPairs, error) { keyPairs["ENVETCD_HOSTNAME"] = config.Hostname } - if config.UseDefaultGateway && gatewayIP != nil { - keyPairs["ENVETCD_DEFAULT_GATEWAY"] = gatewayIP.String() + if config.Etcd.UseDefaultGateway && len(config.Etcd.Peers) == 1 { + keyPairs["ENVETCD_DEFAULT_GATEWAY"] = config.Etcd.Peers[0] } var keys []string @@ -223,6 +305,8 @@ func GetKeyPairs(config *Config) (KeyPairs, error) { log.Printf("[DEBUG] envetcd: %v => %v\n", key, keyPairs[key]) } + processTemplates(keyPairs, config.TemplateFiles) + return keyPairs, nil }