k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cluster/images/etcd/migrate/options.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "regexp" 24 "strings" 25 26 flag "github.com/spf13/pflag" 27 "k8s.io/klog/v2" 28 ) 29 30 var ( 31 supportedEtcdVersions = []string{"3.4.18", "3.5.13"} 32 ) 33 34 const ( 35 etcdNameEnv = "ETCD_NAME" 36 etcdHostnameEnv = "ETCD_HOSTNAME" 37 hostnameEnv = "HOSTNAME" 38 dataDirEnv = "DATA_DIRECTORY" 39 initialClusterEnv = "INITIAL_CLUSTER" 40 initialClusterFmt = "%s=http://localhost:%d" 41 peerListenUrlsEnv = "LISTEN_PEER_URLS" 42 peerListenUrlsFmt = "http://localhost:%d" 43 peerAdvertiseUrlsEnv = "INITIAL_ADVERTISE_PEER_URLS" 44 peerAdvertiseUrlsFmt = "http://localhost:%d" 45 clientListenURLsEnv = "LISTEN_CLIENT_URLS" 46 clientListenURLFmt = "http://127.0.0.1:%d" 47 targetVersionEnv = "TARGET_VERSION" 48 targetStorageEnv = "TARGET_STORAGE" 49 etcdDataPrefixEnv = "ETCD_DATA_PREFIX" 50 etcdDataPrefixDefault = "/registry" 51 ttlKeysDirectoryFmt = "%s/events" 52 etcdServerArgsEnv = "ETCD_CREDS" 53 ) 54 55 type migrateOpts struct { 56 name string 57 port uint64 58 peerPort uint64 59 peerListenUrls string 60 peerAdvertiseUrls string 61 binDir string 62 dataDir string 63 bundledVersions []string 64 supportedVersions SupportedVersions 65 etcdDataPrefix string 66 ttlKeysDirectory string 67 initialCluster string 68 targetVersion string 69 targetStorage string 70 etcdServerArgs string 71 clientListenUrls string 72 } 73 74 func registerFlags(flags *flag.FlagSet, opt *migrateOpts) { 75 flags.StringVar(&opts.name, "name", "", 76 "etcd cluster member name. If unset fallbacks to defaults to ETCD_NAME env, if unset defaults to etcd-<ETCD_HOSTNAME> env, if unset defaults to etcd-<HOSTNAME> env.") 77 flags.Uint64Var(&opts.port, "port", 0, 78 "etcd client port to use during migration operations. "+ 79 "This should be a different port than typically used by etcd to avoid clients accidentally connecting during upgrade/downgrade operations. "+ 80 "If unset default to 18629 or 18631 depending on <data-dir>.") 81 flags.Uint64Var(&opts.peerPort, "peer-port", 0, 82 "etcd peer port to use during migration operations. If unset defaults to 2380 or 2381 depending on <data-dir>.") 83 flags.StringVar(&opts.peerListenUrls, "listen-peer-urls", "", 84 "etcd --listen-peer-urls flag. If unset, fallbacks to LISTEN_PEER_URLS env and if unset defaults to http://localhost:<peer-port>.") 85 flags.StringVar(&opts.peerAdvertiseUrls, "initial-advertise-peer-urls", "", 86 "etcd --initial-advertise-peer-urls flag. If unset fallbacks to INITIAL_ADVERTISE_PEER_URLS env and if unset defaults to http://localhost:<peer-port>.") 87 flags.StringVar(&opts.clientListenUrls, "listen-client-urls", "", 88 "etcd --listen-client-urls flag. If unset, fallbacks to LISTEN_CLIENT_URLS env, and if unset defaults to http://127.0.0.1:<port>.") 89 flags.StringVar(&opts.binDir, "bin-dir", "/usr/local/bin", 90 "directory of etcd and etcdctl binaries, must contain etcd-<version> and etcdctl-<version> for each version listed in <bundled-versions>.") 91 flags.StringVar(&opts.dataDir, "data-dir", "", 92 "etcd data directory of etcd server to migrate. If unset fallbacks to DATA_DIRECTORY env.") 93 flags.StringSliceVar(&opts.bundledVersions, "bundled-versions", supportedEtcdVersions, 94 "comma separated list of etcd binary versions present under the bin-dir.") 95 flags.StringVar(&opts.etcdDataPrefix, "etcd-data-prefix", "", 96 "etcd key prefix under which all objects are kept. If unset fallbacks to ETCD_DATA_PREFIX env and if unset defaults to /registry.") 97 flags.StringVar(&opts.ttlKeysDirectory, "ttl-keys-directory", "", 98 "etcd key prefix under which all keys with TTLs are kept. Defaults to <etcd-data-prefix>/events") 99 flags.StringVar(&opts.initialCluster, "initial-cluster", "", 100 "comma separated list of name=endpoint pairs. If unset fallbacks to INITIAL_CLUSTER and if unset defaults to <etcd-name>=https://localhost:<peer-port>.") 101 flags.StringVar(&opts.targetVersion, "target-version", "", 102 "version of etcd to migrate to. Format must be <major>.<minor>.<patch>. If unset fallbacks to TARGET_VERSION env.") 103 flags.StringVar(&opts.targetStorage, "target-storage", "", 104 "storage version of etcd to migrate to, one of: etcd2, etcd3. If unset fallbacks to TARGET_STORAGE env.") 105 flags.StringVar(&opts.etcdServerArgs, "etcd-server-extra-args", "", 106 "additional etcd server args for starting etcd servers during migration steps, need to set TLS certs flags for multi-member clusters using mTLS for communication. "+ 107 "If unset fallbacks to ETCD_CREDS env.") 108 } 109 110 func lookupEnv(env string) (string, error) { 111 result, ok := os.LookupEnv(env) 112 if !ok || len(result) == 0 { 113 return result, fmt.Errorf("%s variable unset - expected failure", env) 114 } 115 return result, nil 116 } 117 118 func fallbackToEnv(flag, env string) (string, error) { 119 klog.Infof("--%s unset - falling back to %s variable", flag, env) 120 return lookupEnv(env) 121 } 122 123 func fallbackToEnvWithDefault(flag, env, def string) string { 124 if value, err := lookupEnv(env); err == nil { 125 return value 126 } 127 klog.Warningf("%s variable for %s flag unset - defaulting to %s", env, flag, def) 128 return def 129 } 130 131 func defaultName() (string, error) { 132 if etcdName, err := lookupEnv(etcdNameEnv); err == nil { 133 return etcdName, nil 134 } 135 klog.Warningf("%s variable unset - falling back to etcd-%s variable", etcdNameEnv, etcdHostnameEnv) 136 if etcdHostname, err := lookupEnv(etcdHostnameEnv); err == nil { 137 return fmt.Sprintf("etcd-%s", etcdHostname), nil 138 } 139 klog.Warningf("%s variable unset - falling back to etcd-%s variable", etcdHostnameEnv, hostnameEnv) 140 if hostname, err := lookupEnv(hostnameEnv); err == nil { 141 return fmt.Sprintf("etcd-%s", hostname), nil 142 } 143 return "", fmt.Errorf("defaulting --name failed due to all ETCD_NAME, ETCD_HOSTNAME and HOSTNAME unset") 144 } 145 146 func (opts *migrateOpts) validateAndDefault() error { 147 var err error 148 149 if opts.name == "" { 150 klog.Infof("--name unset - falling back to %s variable", etcdNameEnv) 151 if opts.name, err = defaultName(); err != nil { 152 return err 153 } 154 } 155 156 if opts.dataDir == "" { 157 if opts.dataDir, err = fallbackToEnv("data-dir", dataDirEnv); err != nil { 158 return err 159 } 160 } 161 162 etcdEventsRE := regexp.MustCompile("event") 163 if opts.port == 0 { 164 if etcdEventsRE.MatchString(opts.dataDir) { 165 opts.port = 18631 166 } else { 167 opts.port = 18629 168 } 169 klog.Infof("--port unset - defaulting to %d", opts.port) 170 } 171 if opts.peerPort == 0 { 172 if etcdEventsRE.MatchString(opts.dataDir) { 173 opts.peerPort = 2381 174 } else { 175 opts.peerPort = 2380 176 } 177 klog.Infof("--peer-port unset - defaulting to %d", opts.peerPort) 178 } 179 180 if opts.initialCluster == "" { 181 def := fmt.Sprintf(initialClusterFmt, opts.name, opts.peerPort) 182 opts.initialCluster = fallbackToEnvWithDefault("initial-cluster", initialClusterEnv, def) 183 } 184 185 if opts.peerListenUrls == "" { 186 def := fmt.Sprintf(peerListenUrlsFmt, opts.peerPort) 187 opts.peerListenUrls = fallbackToEnvWithDefault("listen-peer-urls", peerListenUrlsEnv, def) 188 } 189 190 if opts.peerAdvertiseUrls == "" { 191 def := fmt.Sprintf(peerAdvertiseUrlsFmt, opts.peerPort) 192 opts.peerAdvertiseUrls = fallbackToEnvWithDefault("initial-advertise-peer-urls", peerAdvertiseUrlsEnv, def) 193 } 194 195 if opts.clientListenUrls == "" { 196 def := fmt.Sprintf(clientListenURLFmt, opts.port) 197 opts.clientListenUrls = fallbackToEnvWithDefault("listen-client-urls", clientListenURLsEnv, def) 198 } 199 200 if opts.targetVersion == "" { 201 if opts.targetVersion, err = fallbackToEnv("target-version", targetVersionEnv); err != nil { 202 return err 203 } 204 } 205 206 if opts.targetStorage == "" { 207 if opts.targetStorage, err = fallbackToEnv("target-storage", targetStorageEnv); err != nil { 208 return err 209 } 210 } 211 212 if opts.etcdDataPrefix == "" { 213 opts.etcdDataPrefix = fallbackToEnvWithDefault("etcd-data-prefix", etcdDataPrefixEnv, etcdDataPrefixDefault) 214 } 215 216 if opts.ttlKeysDirectory == "" { 217 opts.ttlKeysDirectory = fmt.Sprintf(ttlKeysDirectoryFmt, opts.etcdDataPrefix) 218 klog.Infof("--ttl-keys-directory unset - defaulting to %s", opts.ttlKeysDirectory) 219 } 220 221 if opts.etcdServerArgs == "" { 222 opts.etcdServerArgs = fallbackToEnvWithDefault("etcd-server-extra-args", etcdServerArgsEnv, "") 223 } 224 225 if opts.supportedVersions, err = ParseSupportedVersions(opts.bundledVersions); err != nil { 226 return fmt.Errorf("failed to parse --bundled-versions: %v", err) 227 } 228 229 if err := validateBundledVersions(opts.supportedVersions, opts.binDir); err != nil { 230 return fmt.Errorf("failed to validate that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in --bin-dir '%s' for all --bundled-versions '%s': %v", 231 opts.binDir, strings.Join(opts.bundledVersions, ","), err) 232 } 233 return nil 234 } 235 236 // validateBundledVersions checks that 'etcd-<version>' and 'etcdctl-<version>' binaries exist in the binDir 237 // for each version in the bundledVersions list. 238 func validateBundledVersions(bundledVersions SupportedVersions, binDir string) error { 239 for _, v := range bundledVersions { 240 for _, binaryName := range []string{"etcd", "etcdctl"} { 241 fn := filepath.Join(binDir, fmt.Sprintf("%s-%s", binaryName, v)) 242 if _, err := os.Stat(fn); err != nil { 243 return fmt.Errorf("failed to validate '%s' binary exists for bundled-version '%s': %v", fn, v, err) 244 } 245 246 } 247 } 248 return nil 249 }