blob: b48921c6dcdeeb76a1d1395ddb6ccb6d30271212 [file] [log] [blame]
Abhay Kumar40252eb2025-10-13 13:25:53 +00001// 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
15// Package flags implements command-line flag parsing.
16package flags
17
18import (
19 "flag"
20 "fmt"
21 "os"
22 "strconv"
23 "strings"
24
25 "github.com/spf13/pflag"
26 "go.uber.org/zap"
27)
28
29// SetFlagsFromEnv parses all registered flags in the given flagset,
30// and if they are not already set it attempts to set their values from
31// environment variables. Environment variables take the name of the flag but
32// are UPPERCASE, have the given prefix and any dashes are replaced by
33// underscores - for example: some-flag => ETCD_SOME_FLAG
34func SetFlagsFromEnv(lg *zap.Logger, prefix string, fs *flag.FlagSet) error {
35 var err error
36 alreadySet := make(map[string]bool)
37 fs.Visit(func(f *flag.Flag) {
38 alreadySet[FlagToEnv(prefix, f.Name)] = true
39 })
40 usedEnvKey := make(map[string]bool)
41 fs.VisitAll(func(f *flag.Flag) {
42 if serr := setFlagFromEnv(lg, fs, prefix, f.Name, usedEnvKey, alreadySet, true); serr != nil {
43 err = serr
44 }
45 })
46 verifyEnv(lg, prefix, usedEnvKey, alreadySet)
47 return err
48}
49
50// SetPflagsFromEnv is similar to SetFlagsFromEnv. However, the accepted flagset type is pflag.FlagSet
51// and it does not do any logging.
52func SetPflagsFromEnv(lg *zap.Logger, prefix string, fs *pflag.FlagSet) error {
53 var err error
54 alreadySet := make(map[string]bool)
55 usedEnvKey := make(map[string]bool)
56 fs.VisitAll(func(f *pflag.Flag) {
57 if f.Changed {
58 alreadySet[FlagToEnv(prefix, f.Name)] = true
59 }
60 if serr := setFlagFromEnv(lg, fs, prefix, f.Name, usedEnvKey, alreadySet, false); serr != nil {
61 err = serr
62 }
63 })
64 verifyEnv(lg, prefix, usedEnvKey, alreadySet)
65 return err
66}
67
68// FlagToEnv converts flag string to upper-case environment variable key string.
69func FlagToEnv(prefix, name string) string {
70 return prefix + "_" + strings.ToUpper(strings.ReplaceAll(name, "-", "_"))
71}
72
73func verifyEnv(lg *zap.Logger, prefix string, usedEnvKey, alreadySet map[string]bool) {
74 for _, env := range os.Environ() {
75 kv := strings.SplitN(env, "=", 2)
76 if len(kv) != 2 {
77 if lg != nil {
78 lg.Warn("found invalid environment variable", zap.String("environment-variable", env))
79 }
80 }
81 if usedEnvKey[kv[0]] {
82 continue
83 }
84 if alreadySet[kv[0]] {
85 if lg != nil {
86 lg.Fatal(
87 "conflicting environment variable is shadowed by corresponding command-line flag (either unset environment variable or disable flag))",
88 zap.String("environment-variable", kv[0]),
89 )
90 }
91 }
92 if strings.HasPrefix(env, prefix+"_") {
93 if lg != nil {
94 lg.Warn("unrecognized environment variable", zap.String("environment-variable", env))
95 }
96 }
97 }
98}
99
100type flagSetter interface {
101 Set(fk string, fv string) error
102}
103
104func setFlagFromEnv(lg *zap.Logger, fs flagSetter, prefix, fname string, usedEnvKey, alreadySet map[string]bool, log bool) error {
105 key := FlagToEnv(prefix, fname)
106 if !alreadySet[key] {
107 val := os.Getenv(key)
108 if val != "" {
109 usedEnvKey[key] = true
110 if serr := fs.Set(fname, val); serr != nil {
111 return fmt.Errorf("invalid value %q for %s: %w", val, key, serr)
112 }
113 if log && lg != nil {
114 lg.Info(
115 "recognized and used environment variable",
116 zap.String("variable-name", key),
117 zap.String("variable-value", val),
118 )
119 }
120 }
121 }
122 return nil
123}
124
125func IsSet(fs *flag.FlagSet, name string) bool {
126 set := false
127 fs.Visit(func(f *flag.Flag) {
128 if f.Name == name {
129 set = true
130 }
131 })
132 return set
133}
134
135// GetBoolFlagVal returns the value of the a given bool flag if it is explicitly set
136// in the cmd line arguments, otherwise returns nil.
137func GetBoolFlagVal(fs *flag.FlagSet, flagName string) (*bool, error) {
138 if !IsSet(fs, flagName) {
139 return nil, nil
140 }
141 flagVal, parseErr := strconv.ParseBool(fs.Lookup(flagName).Value.String())
142 if parseErr != nil {
143 return nil, parseErr
144 }
145 return &flagVal, nil
146}