diff --git a/e2e/ctl_v3_kv_test.go b/e2e/ctl_v3_kv_test.go index 6039837a9..160d877f0 100644 --- a/e2e/ctl_v3_kv_test.go +++ b/e2e/ctl_v3_kv_test.go @@ -25,6 +25,9 @@ func TestCtlV3PutClientTLS(t *testing.T) { testCtl(t, putTest, withCfg(confi func TestCtlV3PutClientAutoTLS(t *testing.T) { testCtl(t, putTest, withCfg(configClientAutoTLS)) } func TestCtlV3PutPeerTLS(t *testing.T) { testCtl(t, putTest, withCfg(configPeerTLS)) } func TestCtlV3PutTimeout(t *testing.T) { testCtl(t, putTest, withDialTimeout(0)) } +func TestCtlV3PutClientTLSFlagByEnv(t *testing.T) { + testCtl(t, putTest, withCfg(configClientTLS), withFlagByEnv()) +} func TestCtlV3Get(t *testing.T) { testCtl(t, getTest) } func TestCtlV3GetNoTLS(t *testing.T) { testCtl(t, getTest, withCfg(configNoTLS)) } diff --git a/e2e/ctl_v3_test.go b/e2e/ctl_v3_test.go index 901c25c51..24de4e568 100644 --- a/e2e/ctl_v3_test.go +++ b/e2e/ctl_v3_test.go @@ -15,11 +15,13 @@ package e2e import ( + "fmt" "os" "strings" "testing" "time" + "github.com/coreos/etcd/pkg/flags" "github.com/coreos/etcd/pkg/testutil" "github.com/coreos/etcd/version" ) @@ -57,6 +59,8 @@ type ctlCtx struct { epc *etcdProcessCluster + envMap map[string]struct{} + dialTimeout time.Duration quorum bool // if true, set up 3-node cluster and linearizable read @@ -105,6 +109,10 @@ func withNoStrictReconfig() ctlOption { return func(cx *ctlCtx) { cx.noStrictReconfig = true } } +func withFlagByEnv() ctlOption { + return func(cx *ctlCtx) { cx.envMap = make(map[string]struct{}) } +} + func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { defer testutil.AfterTest(t) @@ -133,6 +141,11 @@ func testCtl(t *testing.T, testFunc func(ctlCtx), opts ...ctlOption) { defer func() { os.Unsetenv("ETCDCTL_API") + if ret.envMap != nil { + for k := range ret.envMap { + os.Unsetenv(k) + } + } if errC := ret.epc.Close(); errC != nil { t.Fatalf("error closing etcd processes (%v)", errC) } @@ -160,22 +173,40 @@ func (cx *ctlCtx) prefixArgs(eps []string) []string { panic("v3 proxy not implemented") } - cmdArgs := []string{ctlBinPath, "--endpoints", strings.Join(eps, ","), "--dial-timeout", cx.dialTimeout.String()} + fmap := make(map[string]string) + fmap["endpoints"] = strings.Join(eps, ",") + fmap["dial-timeout"] = cx.dialTimeout.String() if cx.epc.cfg.clientTLS == clientTLS { if cx.epc.cfg.isClientAutoTLS { - cmdArgs = append(cmdArgs, "--insecure-transport=false", "--insecure-skip-tls-verify") + fmap["insecure-transport"] = "false" + fmap["insecure-skip-tls-verify"] = "true" } else { - cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) + fmap["cacert"] = caPath + fmap["cert"] = certPath + fmap["key"] = privateKeyPath } } - if cx.user != "" { - cmdArgs = append(cmdArgs, "--user="+cx.user+":"+cx.pass) + fmap["user"] = cx.user + ":" + cx.pass } + useEnv := cx.envMap != nil + + cmdArgs := []string{ctlBinPath} + for k, v := range fmap { + if useEnv { + ek := flags.FlagToEnv("ETCDCTL", k) + os.Setenv(ek, v) + cx.envMap[ek] = struct{}{} + } else { + cmdArgs = append(cmdArgs, fmt.Sprintf("--%s=%s", k, v)) + } + } return cmdArgs } +// PrefixArgs prefixes etcdctl command. +// Make sure to unset environment variables after tests. func (cx *ctlCtx) PrefixArgs() []string { return cx.prefixArgs(cx.epc.grpcEndpoints()) } diff --git a/etcdctl/README.md b/etcdctl/README.md index b03c282c1..7bd66dadc 100644 --- a/etcdctl/README.md +++ b/etcdctl/README.md @@ -4,6 +4,17 @@ etcdctl `etcdctl` is a command line client for [etcd][etcd]. Make sure to set environment variable `ETCDCTL_API=3`. For etcdctl v2, please check [READMEv2][READMEv2]. +Global flags (e.g., `dial-timeout`, `--cacert`, `--cert`, `--key`) can be set with environment variables: + +``` +ETCDCTL_DIAL_TIMEOUT=3s +ETCDCTL_CACERT=/tmp/ca.pem +ETCDCTL_CERT=/tmp/cert.pem +ETCDCTL_KEY=/tmp/key.pem +``` + +Prefix flag strings with `ETCDCTL_`, convert all letters to upper-case, and replace dash(`-`) with underscore(`_`). + ## Commands ### VERSION diff --git a/pkg/flags/flag.go b/pkg/flags/flag.go index 5631ef775..c5c531268 100644 --- a/pkg/flags/flag.go +++ b/pkg/flags/flag.go @@ -74,7 +74,7 @@ func SetFlagsFromEnv(prefix string, fs *flag.FlagSet) error { var err error alreadySet := make(map[string]bool) fs.Visit(func(f *flag.Flag) { - alreadySet[flagToEnv(prefix, f.Name)] = true + alreadySet[FlagToEnv(prefix, f.Name)] = true }) usedEnvKey := make(map[string]bool) fs.VisitAll(func(f *flag.Flag) { @@ -94,7 +94,7 @@ func SetPflagsFromEnv(prefix string, fs *pflag.FlagSet) error { usedEnvKey := make(map[string]bool) fs.VisitAll(func(f *pflag.Flag) { if f.Changed { - alreadySet[flagToEnv(prefix, f.Name)] = true + alreadySet[FlagToEnv(prefix, f.Name)] = true } if serr := setFlagFromEnv(fs, prefix, f.Name, usedEnvKey, alreadySet, false); serr != nil { err = serr @@ -103,7 +103,8 @@ func SetPflagsFromEnv(prefix string, fs *pflag.FlagSet) error { return err } -func flagToEnv(prefix, name string) string { +// FlagToEnv converts flag string to upper-case environment variable key string. +func FlagToEnv(prefix, name string) string { return prefix + "_" + strings.ToUpper(strings.Replace(name, "-", "_", -1)) } @@ -131,7 +132,7 @@ type flagSetter interface { } func setFlagFromEnv(fs flagSetter, prefix, fname string, usedEnvKey, alreadySet map[string]bool, log bool) error { - key := flagToEnv(prefix, fname) + key := FlagToEnv(prefix, fname) if !alreadySet[key] { val := os.Getenv(key) if val != "" {