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  }