flag.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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. return err
  89. }
  90. func flagToEnv(prefix, name string) string {
  91. return prefix + "_" + strings.ToUpper(strings.Replace(name, "-", "_", -1))
  92. }
  93. func verifyEnv(prefix string, usedEnvKey, alreadySet map[string]bool) {
  94. for _, env := range os.Environ() {
  95. kv := strings.SplitN(env, "=", 2)
  96. if len(kv) != 2 {
  97. plog.Warningf("found invalid env %s", env)
  98. }
  99. if usedEnvKey[kv[0]] {
  100. continue
  101. }
  102. if alreadySet[kv[0]] {
  103. plog.Infof("recognized environment variable %s, but unused: shadowed by corresponding flag ", kv[0])
  104. continue
  105. }
  106. if strings.HasPrefix(env, prefix) {
  107. plog.Warningf("unrecognized environment variable %s", env)
  108. }
  109. }
  110. }
  111. type flagSetter interface {
  112. Set(fk string, fv string) error
  113. }
  114. func setFlagFromEnv(fs flagSetter, prefix, fname string, usedEnvKey, alreadySet map[string]bool, log bool) error {
  115. key := flagToEnv(prefix, fname)
  116. if !alreadySet[key] {
  117. val := os.Getenv(key)
  118. if val != "" {
  119. usedEnvKey[key] = true
  120. if serr := fs.Set(fname, val); serr != nil {
  121. return fmt.Errorf("invalid value %q for %s: %v", val, key, serr)
  122. }
  123. if log {
  124. plog.Infof("recognized and used environment variable %s=%s", key, val)
  125. }
  126. }
  127. }
  128. return nil
  129. }
  130. // URLsFromFlag returns a slices from url got from the flag.
  131. func URLsFromFlag(fs *flag.FlagSet, urlsFlagName string) []url.URL {
  132. return []url.URL(*fs.Lookup(urlsFlagName).Value.(*URLsValue))
  133. }
  134. func IsSet(fs *flag.FlagSet, name string) bool {
  135. set := false
  136. fs.Visit(func(f *flag.Flag) {
  137. if f.Name == name {
  138. set = true
  139. }
  140. })
  141. return set
  142. }