flag.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Copyright 2015 The etcd Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package flags implements command-line flag parsing.
  15. package flags
  16. import (
  17. "flag"
  18. "fmt"
  19. "net/url"
  20. "os"
  21. "strings"
  22. "github.com/coreos/pkg/capnslog"
  23. "github.com/spf13/pflag"
  24. )
  25. var (
  26. plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "pkg/flags")
  27. )
  28. // DeprecatedFlag encapsulates a flag that may have been previously valid but
  29. // is now deprecated. If a DeprecatedFlag is set, an error occurs.
  30. type DeprecatedFlag struct {
  31. Name string
  32. }
  33. func (f *DeprecatedFlag) Set(_ string) error {
  34. return fmt.Errorf(`flag "-%s" is no longer supported.`, f.Name)
  35. }
  36. func (f *DeprecatedFlag) String() string {
  37. return ""
  38. }
  39. // IgnoredFlag encapsulates a flag that may have been previously valid but is
  40. // now ignored. If an IgnoredFlag is set, a warning is printed and
  41. // operation continues.
  42. type IgnoredFlag struct {
  43. Name string
  44. }
  45. // IsBoolFlag is defined to allow the flag to be defined without an argument
  46. func (f *IgnoredFlag) IsBoolFlag() bool {
  47. return true
  48. }
  49. func (f *IgnoredFlag) Set(s string) error {
  50. plog.Warningf(`flag "-%s" is no longer supported - ignoring.`, f.Name)
  51. return nil
  52. }
  53. func (f *IgnoredFlag) String() string {
  54. return ""
  55. }
  56. // SetFlagsFromEnv parses all registered flags in the given flagset,
  57. // and if they are not already set it attempts to set their values from
  58. // environment variables. Environment variables take the name of the flag but
  59. // are UPPERCASE, have the given prefix and any dashes are replaced by
  60. // underscores - for example: some-flag => ETCD_SOME_FLAG
  61. func SetFlagsFromEnv(prefix string, fs *flag.FlagSet) error {
  62. var err error
  63. alreadySet := make(map[string]bool)
  64. fs.Visit(func(f *flag.Flag) {
  65. alreadySet[FlagToEnv(prefix, f.Name)] = true
  66. })
  67. usedEnvKey := make(map[string]bool)
  68. fs.VisitAll(func(f *flag.Flag) {
  69. err = setFlagFromEnv(fs, prefix, f.Name, usedEnvKey, alreadySet, true)
  70. })
  71. verifyEnv(prefix, usedEnvKey, alreadySet)
  72. return err
  73. }
  74. // SetPflagsFromEnv is similar to SetFlagsFromEnv. However, the accepted flagset type is pflag.FlagSet
  75. // and it does not do any logging.
  76. func SetPflagsFromEnv(prefix string, fs *pflag.FlagSet) error {
  77. var err error
  78. alreadySet := make(map[string]bool)
  79. usedEnvKey := make(map[string]bool)
  80. fs.VisitAll(func(f *pflag.Flag) {
  81. if f.Changed {
  82. alreadySet[FlagToEnv(prefix, f.Name)] = true
  83. }
  84. if serr := setFlagFromEnv(fs, prefix, f.Name, usedEnvKey, alreadySet, false); serr != nil {
  85. err = serr
  86. }
  87. })
  88. verifyEnv(prefix, usedEnvKey, alreadySet)
  89. return err
  90. }
  91. // FlagToEnv converts flag string to upper-case environment variable key string.
  92. func FlagToEnv(prefix, name string) string {
  93. return prefix + "_" + strings.ToUpper(strings.Replace(name, "-", "_", -1))
  94. }
  95. func verifyEnv(prefix string, usedEnvKey, alreadySet map[string]bool) {
  96. for _, env := range os.Environ() {
  97. kv := strings.SplitN(env, "=", 2)
  98. if len(kv) != 2 {
  99. plog.Warningf("found invalid env %s", env)
  100. }
  101. if usedEnvKey[kv[0]] {
  102. continue
  103. }
  104. if alreadySet[kv[0]] {
  105. // TODO: exit with error in v3.4
  106. plog.Warningf("recognized environment variable %s, but unused: shadowed by corresponding flag", kv[0])
  107. continue
  108. }
  109. if strings.HasPrefix(env, prefix+"_") {
  110. plog.Warningf("unrecognized environment variable %s", env)
  111. }
  112. }
  113. }
  114. type flagSetter interface {
  115. Set(fk string, fv string) error
  116. }
  117. func setFlagFromEnv(fs flagSetter, prefix, fname string, usedEnvKey, alreadySet map[string]bool, log bool) error {
  118. key := FlagToEnv(prefix, fname)
  119. if !alreadySet[key] {
  120. val := os.Getenv(key)
  121. if val != "" {
  122. usedEnvKey[key] = true
  123. if serr := fs.Set(fname, val); serr != nil {
  124. return fmt.Errorf("invalid value %q for %s: %v", val, key, serr)
  125. }
  126. if log {
  127. plog.Infof("recognized and used environment variable %s=%s", key, val)
  128. }
  129. }
  130. }
  131. return nil
  132. }
  133. // URLsFromFlag returns a slices from url got from the flag.
  134. func URLsFromFlag(fs *flag.FlagSet, urlsFlagName string) []url.URL {
  135. return []url.URL(*fs.Lookup(urlsFlagName).Value.(*URLsValue))
  136. }
  137. func IsSet(fs *flag.FlagSet, name string) bool {
  138. set := false
  139. fs.Visit(func(f *flag.Flag) {
  140. if f.Name == name {
  141. set = true
  142. }
  143. })
  144. return set
  145. }