1// Copyright 2015 CoreOS, Inc. 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 "net/url" 22 "os" 23 "strings" 24 25 "github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/pkg/capnslog" 26 "github.com/coreos/etcd/pkg/transport" 27) 28 29var ( 30 plog = capnslog.NewPackageLogger("github.com/coreos/etcd/pkg", "flags") 31) 32 33// DeprecatedFlag encapsulates a flag that may have been previously valid but 34// is now deprecated. If a DeprecatedFlag is set, an error occurs. 35type DeprecatedFlag struct { 36 Name string 37} 38 39func (f *DeprecatedFlag) Set(_ string) error { 40 return fmt.Errorf(`flag "-%s" is no longer supported.`, f.Name) 41} 42 43func (f *DeprecatedFlag) String() string { 44 return "" 45} 46 47// IgnoredFlag encapsulates a flag that may have been previously valid but is 48// now ignored. If an IgnoredFlag is set, a warning is printed and 49// operation continues. 50type IgnoredFlag struct { 51 Name string 52} 53 54// IsBoolFlag is defined to allow the flag to be defined without an argument 55func (f *IgnoredFlag) IsBoolFlag() bool { 56 return true 57} 58 59func (f *IgnoredFlag) Set(s string) error { 60 plog.Warningf(`flag "-%s" is no longer supported - ignoring.`, f.Name) 61 return nil 62} 63 64func (f *IgnoredFlag) String() string { 65 return "" 66} 67 68// SetFlagsFromEnv parses all registered flags in the given flagset, 69// and if they are not already set it attempts to set their values from 70// environment variables. Environment variables take the name of the flag but 71// are UPPERCASE, have the prefix "ETCD_", and any dashes are replaced by 72// underscores - for example: some-flag => ETCD_SOME_FLAG 73func SetFlagsFromEnv(fs *flag.FlagSet) error { 74 var err error 75 alreadySet := make(map[string]bool) 76 fs.Visit(func(f *flag.Flag) { 77 alreadySet[flagToEnv(f.Name)] = true 78 }) 79 usedEnvKey := make(map[string]bool) 80 fs.VisitAll(func(f *flag.Flag) { 81 key := flagToEnv(f.Name) 82 if !alreadySet[key] { 83 val := os.Getenv(key) 84 if val != "" { 85 usedEnvKey[key] = true 86 if serr := fs.Set(f.Name, val); serr != nil { 87 err = fmt.Errorf("invalid value %q for %s: %v", val, key, serr) 88 } 89 plog.Infof("recognized and used environment variable %s=%s", key, val) 90 } 91 } 92 }) 93 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, "ETCD_") { 107 plog.Warningf("unrecognized environment variable %s", env) 108 } 109 } 110 111 return err 112} 113 114func flagToEnv(name string) string { 115 return "ETCD_" + strings.ToUpper(strings.Replace(name, "-", "_", -1)) 116} 117 118// SetBindAddrFromAddr sets the value of bindAddr flag from the value 119// of addr flag. Both flags' Value must be of type IPAddressPort. If the 120// bindAddr flag is set and the addr flag is unset, it will set bindAddr to 121// [::]:port of addr. Otherwise, it keeps the original values. 122func SetBindAddrFromAddr(fs *flag.FlagSet, bindAddrFlagName, addrFlagName string) { 123 if IsSet(fs, bindAddrFlagName) || !IsSet(fs, addrFlagName) { 124 return 125 } 126 addr := *fs.Lookup(addrFlagName).Value.(*IPAddressPort) 127 addr.IP = "::" 128 if err := fs.Set(bindAddrFlagName, addr.String()); err != nil { 129 plog.Panicf("unexpected flags set error: %v", err) 130 } 131} 132 133// URLsFromFlags decides what URLs should be using two different flags 134// as datasources. The first flag's Value must be of type URLs, while 135// the second must be of type IPAddressPort. If both of these flags 136// are set, an error will be returned. If only the first flag is set, 137// the underlying url.URL objects will be returned unmodified. If the 138// second flag happens to be set, the underlying IPAddressPort will be 139// converted to a url.URL and returned. The Scheme of the returned 140// url.URL will be http unless the provided TLSInfo object is non-empty. 141// If neither of the flags have been explicitly set, the default value 142// of the first flag will be returned unmodified. 143func URLsFromFlags(fs *flag.FlagSet, urlsFlagName string, addrFlagName string, tlsInfo transport.TLSInfo) ([]url.URL, error) { 144 visited := make(map[string]struct{}) 145 fs.Visit(func(f *flag.Flag) { 146 visited[f.Name] = struct{}{} 147 }) 148 149 _, urlsFlagIsSet := visited[urlsFlagName] 150 _, addrFlagIsSet := visited[addrFlagName] 151 152 if addrFlagIsSet { 153 if urlsFlagIsSet { 154 return nil, fmt.Errorf("Set only one of flags -%s and -%s", urlsFlagName, addrFlagName) 155 } 156 157 addr := *fs.Lookup(addrFlagName).Value.(*IPAddressPort) 158 addrURL := url.URL{Scheme: "http", Host: addr.String()} 159 if !tlsInfo.Empty() { 160 addrURL.Scheme = "https" 161 } 162 return []url.URL{addrURL}, nil 163 } 164 165 return []url.URL(*fs.Lookup(urlsFlagName).Value.(*URLsValue)), nil 166} 167 168func IsSet(fs *flag.FlagSet, name string) bool { 169 set := false 170 fs.Visit(func(f *flag.Flag) { 171 if f.Name == name { 172 set = true 173 } 174 }) 175 return set 176} 177