github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/runsc/config/flags.go (about)

     1  // Copyright 2020 The gVisor 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 config
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"reflect"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"text/template"
    27  
    28  	"github.com/metacubex/gvisor/pkg/log"
    29  	"github.com/metacubex/gvisor/pkg/refs"
    30  	"github.com/metacubex/gvisor/pkg/sentry/watchdog"
    31  	"github.com/metacubex/gvisor/runsc/flag"
    32  )
    33  
    34  // RegisterFlags registers flags used to populate Config.
    35  func RegisterFlags(flagSet *flag.FlagSet) {
    36  	// Although these flags are not part of the OCI spec, they are used by
    37  	// Docker, and thus should not be changed.
    38  	flagSet.String("root", "", "root directory for storage of container state.")
    39  	flagSet.String("log", "", "file path where internal debug information is written, default is stdout.")
    40  	flagSet.String("log-format", "text", "log format: text (default), json, or json-k8s.")
    41  	flagSet.Bool("debug", false, "enable debug logging.")
    42  	flagSet.Bool("systemd-cgroup", false, "EXPERIMENTAL. Use systemd for cgroups.")
    43  
    44  	// These flags are unique to runsc, and are used to configure parts of the
    45  	// system that are not covered by the runtime spec.
    46  
    47  	// Debugging flags.
    48  	flagSet.String("debug-log", "", "additional location for logs. If it ends with '/', log files are created inside the directory with default names. The following variables are available: %TIMESTAMP%, %COMMAND%.")
    49  	flagSet.String("debug-command", "", `comma-separated list of commands to be debugged if --debug-log is also set. Empty means debug all. "!" negates the expression. E.g. "create,start" or "!boot,events"`)
    50  	flagSet.String("panic-log", "", "file path where panic reports and other Go's runtime messages are written.")
    51  	flagSet.String("coverage-report", "", "file path where Go coverage reports are written. Reports will only be generated if runsc is built with --collect_code_coverage and --instrumentation_filter Bazel flags.")
    52  	flagSet.Bool("log-packets", false, "enable network packet logging.")
    53  	flagSet.String("pcap-log", "", "location of PCAP log file.")
    54  	flagSet.String("debug-log-format", "text", "log format: text (default), json, or json-k8s.")
    55  	flagSet.Bool("debug-to-user-log", false, "also emit Sentry logs to user-visible logs")
    56  	// Only register -alsologtostderr flag if it is not already defined on this flagSet.
    57  	if flagSet.Lookup("alsologtostderr") == nil {
    58  		flagSet.Bool("alsologtostderr", false, "send log messages to stderr.")
    59  	}
    60  	flagSet.Bool("allow-flag-override", false, "allow OCI annotations (dev.gvisor.flag.<name>) to override flags for debugging.")
    61  	flagSet.String("traceback", "system", "golang runtime's traceback level")
    62  
    63  	// Metrics flags.
    64  	flagSet.String("metric-server", "", "if set, export metrics on this address. This may either be 1) 'addr:port' to export metrics on a specific network interface address, 2) ':port' for exporting metrics on all interfaces, or 3) an absolute path to a Unix Domain Socket. The substring '%ID%' will be replaced by the container ID, and '%RUNTIME_ROOT%' by the root. This flag must be specified in both `runsc metric-server` and `runsc create`, and their values must match.")
    65  	flagSet.String("profiling-metrics", "", "comma separated list of metric names which are going to be written to the profiling-metrics-log file from within the sentry in CSV format. profiling-metrics will be snapshotted at a rate specified by profiling-metrics-rate-us. Requires profiling-metrics-log to be set. (DO NOT USE IN PRODUCTION).")
    66  	flagSet.String("profiling-metrics-log", "", "file name to use for profiling-metrics output. (DO NOT USE IN PRODUCTION)")
    67  	flagSet.Int("profiling-metrics-rate-us", 1000, "the target rate (in microseconds) at which profiling metrics will be snapshotted.")
    68  
    69  	// Debugging flags: strace related
    70  	flagSet.Bool("strace", false, "enable strace.")
    71  	flagSet.String("strace-syscalls", "", "comma-separated list of syscalls to trace. If --strace is true and this list is empty, then all syscalls will be traced.")
    72  	flagSet.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs.")
    73  	flagSet.Bool("strace-event", false, "send strace to event.")
    74  
    75  	// Flags that control sandbox runtime behavior.
    76  	flagSet.String("platform", "systrap", "specifies which platform to use: systrap (default), ptrace, kvm.")
    77  	flagSet.String("platform_device_path", "", "path to a platform-specific device file (e.g. /dev/kvm for KVM platform). If unset, will use a sane platform-specific default.")
    78  	flagSet.Var(watchdogActionPtr(watchdog.LogWarning), "watchdog-action", "sets what action the watchdog takes when triggered: log (default), panic.")
    79  	flagSet.Int("panic-signal", -1, "register signal handling that panics. Usually set to SIGUSR2(12) to troubleshoot hangs. -1 disables it.")
    80  	flagSet.Bool("profile", false, "prepares the sandbox to use Golang profiler. Note that enabling profiler loosens the seccomp protection added to the sandbox (DO NOT USE IN PRODUCTION).")
    81  	flagSet.String("profile-block", "", "collects a block profile to this file path for the duration of the container execution. Requires -profile=true.")
    82  	flagSet.String("profile-cpu", "", "collects a CPU profile to this file path for the duration of the container execution. Requires -profile=true.")
    83  	flagSet.String("profile-heap", "", "collects a heap profile to this file path for the duration of the container execution. Requires -profile=true.")
    84  	flagSet.String("profile-mutex", "", "collects a mutex profile to this file path for the duration of the container execution. Requires -profile=true.")
    85  	flagSet.String("trace", "", "collects a Go runtime execution trace to this file path for the duration of the container execution.")
    86  	flagSet.Bool("rootless", false, "it allows the sandbox to be started with a user that is not root. Sandbox and Gofer processes may run with same privileges as current user.")
    87  	flagSet.Var(leakModePtr(refs.NoLeakChecking), "ref-leak-mode", "sets reference leak check mode: disabled (default), log-names, log-traces.")
    88  	flagSet.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater or equal to quota value, but not less than 2)")
    89  	flagSet.Bool("oci-seccomp", false, "Enables loading OCI seccomp filters inside the sandbox.")
    90  	flagSet.Bool("enable-core-tags", false, "enables core tagging. Requires host linux kernel >= 5.14.")
    91  	flagSet.String("pod-init-config", "", "path to configuration file with additional steps to take during pod creation.")
    92  
    93  	// Flags that control sandbox runtime behavior: FS related.
    94  	flagSet.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem validation to use for the root mount: exclusive (default), shared.")
    95  	flagSet.Var(fileAccessTypePtr(FileAccessShared), "file-access-mounts", "specifies which filesystem validation to use for volumes other than the root mount: shared (default), exclusive.")
    96  	flagSet.Bool("overlay", false, "DEPRECATED: use --overlay2=all:memory to achieve the same effect")
    97  	flagSet.Var(defaultOverlay2(), "overlay2", "wrap mounts with overlayfs. Format is {mount}:{medium}, where 'mount' can be 'root' or 'all' and medium can be 'memory', 'self' or 'dir=/abs/dir/path' in which filestore will be created. 'none' will turn overlay mode off.")
    98  	flagSet.Bool("fsgofer-host-uds", false, "DEPRECATED: use host-uds=all")
    99  	flagSet.Var(hostUDSPtr(HostUDSNone), "host-uds", "controls permission to access host Unix-domain sockets. Values: none|open|create|all, default: none")
   100  	flagSet.Var(hostFifoPtr(HostFifoNone), "host-fifo", "controls permission to access host FIFOs (or named pipes). Values: none|open, default: none")
   101  
   102  	flagSet.Bool("vfs2", true, "DEPRECATED: this flag has no effect.")
   103  	flagSet.Bool("fuse", true, "DEPRECATED: this flag has no effect.")
   104  	flagSet.Bool("lisafs", true, "DEPRECATED: this flag has no effect.")
   105  	flagSet.Bool("cgroupfs", false, "Automatically mount cgroupfs.")
   106  	flagSet.Bool("ignore-cgroups", false, "don't configure cgroups.")
   107  	flagSet.Int("fdlimit", -1, "Specifies a limit on the number of host file descriptors that can be open. Applies separately to the sentry and gofer. Note: each file in the sandbox holds more than one host FD open.")
   108  	flagSet.Int("dcache", -1, "Set the global dentry cache size. This acts as a coarse-grained control on the number of host FDs simultaneously open by the sentry. If negative, per-mount caches are used.")
   109  	flagSet.Bool("iouring", false, "TEST ONLY; Enables io_uring syscalls in the sentry. Support is experimental and very limited.")
   110  	flagSet.Bool("directfs", true, "directly access the container filesystems from the sentry. Sentry runs with higher privileges.")
   111  
   112  	// Flags that control sandbox runtime behavior: network related.
   113  	flagSet.Var(networkTypePtr(NetworkSandbox), "network", "specifies which network to use: sandbox (default), host, none. Using network inside the sandbox is more secure because it's isolated from the host network.")
   114  	flagSet.Bool("net-raw", false, "enable raw sockets. When false, raw sockets are disabled by removing CAP_NET_RAW from containers (`runsc exec` will still be able to utilize raw sockets). Raw sockets allow malicious containers to craft packets and potentially attack the network.")
   115  	flagSet.Bool("gso", true, "enable host segmentation offload if it is supported by a network device.")
   116  	flagSet.Bool("software-gso", true, "enable gVisor segmentation offload when host offload can't be enabled.")
   117  	flagSet.Duration("gvisor-gro", 0, "(e.g. \"20000ns\" or \"1ms\") sets gVisor's generic receive offload timeout. Zero bypasses GRO.")
   118  	flagSet.Bool("tx-checksum-offload", false, "enable TX checksum offload.")
   119  	flagSet.Bool("rx-checksum-offload", true, "enable RX checksum offload.")
   120  	flagSet.Var(queueingDisciplinePtr(QDiscFIFO), "qdisc", "specifies which queueing discipline to apply by default to the non loopback nics used by the sandbox.")
   121  	flagSet.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.")
   122  	flagSet.Bool("buffer-pooling", true, "enable allocation of buffers from a shared pool instead of the heap.")
   123  	flagSet.Var(&xdpConfig, "EXPERIMENTAL-xdp", `whether and how to use XDP. Can be one of: "off" (default), "ns", "redirect:<device name>", or "tunnel:<device name>"`)
   124  	flagSet.Bool("EXPERIMENTAL-xdp-need-wakeup", true, "EXPERIMENTAL. Use XDP_USE_NEED_WAKEUP with XDP sockets.") // TODO(b/240191988): Figure out whether this helps and remove it as a flag.
   125  	flagSet.Bool("reproduce-nat", false, "Scrape the host netns NAT table and reproduce it in the sandbox.")
   126  	flagSet.Bool("reproduce-nftables", false, "Attempt to scrape and reproduce nftable rules inside the sandbox. Overrides reproduce-nat when true.")
   127  
   128  	// Flags that control sandbox runtime behavior: accelerator related.
   129  	flagSet.Bool("nvproxy", false, "EXPERIMENTAL: enable support for Nvidia GPUs")
   130  	flagSet.Bool("nvproxy-docker", false, "DEPRECATED: use nvidia-container-runtime or `docker run --gpus` directly. Or manually add nvidia-container-runtime-hook as a prestart hook and set up NVIDIA_VISIBLE_DEVICES container environment variable.")
   131  	flagSet.Bool("tpuproxy", false, "EXPERIMENTAL: enable support for TPU device passthrough.")
   132  
   133  	// Test flags, not to be used outside tests, ever.
   134  	flagSet.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.")
   135  	flagSet.String("TESTONLY-test-name-env", "", "TEST ONLY; do not ever use! Used for automated tests to improve logging.")
   136  	flagSet.Bool("TESTONLY-allow-packet-endpoint-write", false, "TEST ONLY; do not ever use! Used for tests to allow writes on packet sockets.")
   137  	flagSet.Bool("TESTONLY-afs-syscall-panic", false, "TEST ONLY; do not ever use! Used for tests exercising gVisor panic reporting.")
   138  	flagSet.String("TESTONLY-autosave-image-path", "", "TEST ONLY; enable auto save for syscall tests and set path for state file.")
   139  }
   140  
   141  // overrideAllowlist lists all flags that can be changed using OCI
   142  // annotations without an administrator setting `--allow-flag-override` on the
   143  // runtime. Flags in this list can be set by container authors and should not
   144  // make the sandbox less secure.
   145  var overrideAllowlist = map[string]struct {
   146  	check func(name string, value string) error
   147  }{
   148  	"debug":             {},
   149  	"debug-to-user-log": {},
   150  	"strace":            {},
   151  	"strace-syscalls":   {},
   152  	"strace-log-size":   {},
   153  	"host-uds":          {},
   154  
   155  	"oci-seccomp": {check: checkOciSeccomp},
   156  }
   157  
   158  // checkOciSeccomp ensures that seccomp can be enabled but not disabled.
   159  func checkOciSeccomp(name string, value string) error {
   160  	enable, err := strconv.ParseBool(value)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	if !enable {
   165  		return fmt.Errorf("disabling %q requires flag %q to be enabled", name, "allow-flag-override")
   166  	}
   167  	return nil
   168  }
   169  
   170  // isFlagExplicitlySet returns whether the given flag name is explicitly set.
   171  // Doesn't check for flag existence; returns `false` for flags that don't exist.
   172  func isFlagExplicitlySet(flagSet *flag.FlagSet, name string) bool {
   173  	explicit := false
   174  
   175  	// The FlagSet.Visit function only visits flags that are explicitly set, as opposed to VisitAll.
   176  	flagSet.Visit(func(fl *flag.Flag) {
   177  		explicit = explicit || fl.Name == name
   178  	})
   179  
   180  	return explicit
   181  }
   182  
   183  // NewFromFlags creates a new Config with values coming from command line flags.
   184  func NewFromFlags(flagSet *flag.FlagSet) (*Config, error) {
   185  	conf := &Config{explicitlySet: map[string]struct{}{}}
   186  
   187  	obj := reflect.ValueOf(conf).Elem()
   188  	st := obj.Type()
   189  	for i := 0; i < st.NumField(); i++ {
   190  		f := st.Field(i)
   191  		name, ok := f.Tag.Lookup("flag")
   192  		if !ok {
   193  			// No flag set for this field.
   194  			continue
   195  		}
   196  		fl := flagSet.Lookup(name)
   197  		if fl == nil {
   198  			panic(fmt.Sprintf("Flag %q not found", name))
   199  		}
   200  		x := reflect.ValueOf(flag.Get(fl.Value))
   201  		obj.Field(i).Set(x)
   202  		if isFlagExplicitlySet(flagSet, name) {
   203  			conf.explicitlySet[name] = struct{}{}
   204  		}
   205  	}
   206  
   207  	if len(conf.RootDir) == 0 {
   208  		// If not set, set default root dir to something (hopefully) user-writeable.
   209  		conf.RootDir = "/var/run/runsc"
   210  		// NOTE: empty values for XDG_RUNTIME_DIR should be ignored.
   211  		if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
   212  			conf.RootDir = filepath.Join(runtimeDir, "runsc")
   213  		}
   214  	}
   215  
   216  	if err := conf.validate(); err != nil {
   217  		return nil, err
   218  	}
   219  	return conf, nil
   220  }
   221  
   222  // NewFromBundle makes a new config from a Bundle.
   223  func NewFromBundle(bundle Bundle) (*Config, error) {
   224  	if err := bundle.Validate(); err != nil {
   225  		return nil, err
   226  	}
   227  	flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError)
   228  	RegisterFlags(flagSet)
   229  	conf := &Config{explicitlySet: map[string]struct{}{}}
   230  
   231  	obj := reflect.ValueOf(conf).Elem()
   232  	st := obj.Type()
   233  	for i := 0; i < st.NumField(); i++ {
   234  		f := st.Field(i)
   235  		name, ok := f.Tag.Lookup("flag")
   236  		if !ok {
   237  			continue
   238  		}
   239  		fl := flagSet.Lookup(name)
   240  		if fl == nil {
   241  			return nil, fmt.Errorf("flag %q not found", name)
   242  		}
   243  		val, ok := bundle[name]
   244  		if !ok {
   245  			continue
   246  		}
   247  		if err := flagSet.Set(name, val); err != nil {
   248  			return nil, fmt.Errorf("error setting flag %s=%q: %w", name, val, err)
   249  		}
   250  		conf.Override(flagSet, name, val, true)
   251  
   252  		conf.explicitlySet[name] = struct{}{}
   253  	}
   254  	return conf, nil
   255  }
   256  
   257  // ToFlags returns a slice of flags that correspond to the given Config.
   258  func (c *Config) ToFlags() []string {
   259  	flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError)
   260  	RegisterFlags(flagSet)
   261  
   262  	var rv []string
   263  	keyVals := c.keyVals(flagSet, false /*onlyIfSet*/)
   264  	for name, val := range keyVals {
   265  		rv = append(rv, fmt.Sprintf("--%s=%s", name, val))
   266  	}
   267  
   268  	// Construct a temporary set for default plumbing.
   269  	return rv
   270  }
   271  
   272  // KeyVal is a key value pair. It is used so ToContainerdConfigTOML returns
   273  // predictable ordering for runsc flags.
   274  type KeyVal struct {
   275  	Key string
   276  	Val string
   277  }
   278  
   279  // ContainerdConfigOptions contains arguments for ToContainerdConfigTOML.
   280  type ContainerdConfigOptions struct {
   281  	BinaryPath string
   282  	RootPath   string
   283  	Options    map[string]string
   284  	RunscFlags []KeyVal
   285  }
   286  
   287  // ToContainerdConfigTOML turns a given config into a format for a k8s containerd config.toml file.
   288  // See: https://gvisor.dev/docs/user_guide/containerd/quick_start/
   289  func (c *Config) ToContainerdConfigTOML(opts ContainerdConfigOptions) (string, error) {
   290  	flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError)
   291  	RegisterFlags(flagSet)
   292  	keyVals := c.keyVals(flagSet, true /*onlyIfSet*/)
   293  	keys := []string{}
   294  	for k := range keyVals {
   295  		keys = append(keys, k)
   296  	}
   297  
   298  	sort.Strings(keys)
   299  
   300  	for _, k := range keys {
   301  		opts.RunscFlags = append(opts.RunscFlags, KeyVal{k, keyVals[k]})
   302  	}
   303  
   304  	const temp = `{{if .BinaryPath}}binary_name = "{{.BinaryPath}}"{{end}}
   305  {{if .RootPath}}root = "{{.RootPath}}"{{end}}
   306  {{if .Options}}{{ range $key, $value := .Options}}{{$key}} = "{{$value}}"
   307  {{end}}{{end}}{{if .RunscFlags}}[runsc_config]
   308  {{ range $fl:= .RunscFlags}}  {{$fl.Key}} = "{{$fl.Val}}"
   309  {{end}}{{end}}`
   310  
   311  	t := template.New("temp")
   312  	t, err := t.Parse(temp)
   313  	if err != nil {
   314  		return "", err
   315  	}
   316  	var buf bytes.Buffer
   317  	if err := t.Execute(&buf, opts); err != nil {
   318  		return "", err
   319  	}
   320  	return buf.String(), nil
   321  }
   322  
   323  func (c *Config) keyVals(flagSet *flag.FlagSet, onlyIfSet bool) map[string]string {
   324  	keyVals := make(map[string]string)
   325  
   326  	obj := reflect.ValueOf(c).Elem()
   327  	st := obj.Type()
   328  	for i := 0; i < st.NumField(); i++ {
   329  		f := st.Field(i)
   330  		name, ok := f.Tag.Lookup("flag")
   331  		if !ok {
   332  			// No flag set for this field.
   333  			continue
   334  		}
   335  		val := getVal(obj.Field(i))
   336  
   337  		fl := flagSet.Lookup(name)
   338  		if fl == nil {
   339  			panic(fmt.Sprintf("Flag %q not found", name))
   340  		}
   341  		if val == fl.DefValue || onlyIfSet {
   342  			// If this config wasn't populated from a FlagSet, don't plumb through default flags.
   343  			if c.explicitlySet == nil {
   344  				continue
   345  			}
   346  			// If this config was populated from a FlagSet, plumb through only default flags which were
   347  			// explicitly specified.
   348  			if _, explicit := c.explicitlySet[name]; !explicit {
   349  				continue
   350  			}
   351  		}
   352  		keyVals[fl.Name] = val
   353  	}
   354  	return keyVals
   355  }
   356  
   357  // Override writes a new value to a flag.
   358  func (c *Config) Override(flagSet *flag.FlagSet, name string, value string, force bool) error {
   359  	obj := reflect.ValueOf(c).Elem()
   360  	st := obj.Type()
   361  	for i := 0; i < st.NumField(); i++ {
   362  		f := st.Field(i)
   363  		fieldName, ok := f.Tag.Lookup("flag")
   364  		if !ok || fieldName != name {
   365  			// Not a flag field, or flag name doesn't match.
   366  			continue
   367  		}
   368  		fl := flagSet.Lookup(name)
   369  		if fl == nil {
   370  			// Flag must exist if there is a field match above.
   371  			panic(fmt.Sprintf("Flag %q not found", name))
   372  		}
   373  		if !force {
   374  			if err := c.isOverrideAllowed(name, value); err != nil {
   375  				return fmt.Errorf("error setting flag %s=%q: %w", name, value, err)
   376  			}
   377  		}
   378  
   379  		// Use flag to convert the string value to the underlying flag type, using
   380  		// the same rules as the command-line for consistency.
   381  		if err := fl.Value.Set(value); err != nil {
   382  			return fmt.Errorf("error setting flag %s=%q: %w", name, value, err)
   383  		}
   384  		x := reflect.ValueOf(flag.Get(fl.Value))
   385  		obj.Field(i).Set(x)
   386  
   387  		// Validates the config again to ensure it's left in a consistent state.
   388  		return c.validate()
   389  	}
   390  	return fmt.Errorf("flag %q not found. Cannot set it to %q", name, value)
   391  }
   392  
   393  func (c *Config) isOverrideAllowed(name string, value string) error {
   394  	if c.AllowFlagOverride {
   395  		return nil
   396  	}
   397  	// If the global override flag is not enabled, check if the individual flag is
   398  	// safe to apply.
   399  	if allow, ok := overrideAllowlist[name]; ok {
   400  		if allow.check != nil {
   401  			if err := allow.check(name, value); err != nil {
   402  				return err
   403  			}
   404  		}
   405  		return nil
   406  	}
   407  	return fmt.Errorf("flag override disabled, use --allow-flag-override to enable it")
   408  }
   409  
   410  // ApplyBundles applies the given bundles by name.
   411  // It returns an error if a bundle doesn't exist, or if the given
   412  // bundles have conflicting flag values.
   413  // Config values which are already specified prior to calling ApplyBundles are overridden.
   414  func (c *Config) ApplyBundles(flagSet *flag.FlagSet, bundleNames ...BundleName) error {
   415  	// Populate a map from flag name to flag value to bundle name.
   416  	flagToValueToBundleName := make(map[string]map[string]BundleName)
   417  	for _, bundleName := range bundleNames {
   418  		b := Bundles[bundleName]
   419  		if b == nil {
   420  			return fmt.Errorf("no such bundle: %q", bundleName)
   421  		}
   422  		for flagName, val := range b {
   423  			valueToBundleName := flagToValueToBundleName[flagName]
   424  			if valueToBundleName == nil {
   425  				valueToBundleName = make(map[string]BundleName)
   426  				flagToValueToBundleName[flagName] = valueToBundleName
   427  			}
   428  			valueToBundleName[val] = bundleName
   429  		}
   430  	}
   431  	// Check for conflicting flag values between the bundles.
   432  	for flagName, valueToBundleName := range flagToValueToBundleName {
   433  		if len(valueToBundleName) == 1 {
   434  			continue
   435  		}
   436  		bundleNameToValue := make(map[string]string)
   437  		for val, bundleName := range valueToBundleName {
   438  			bundleNameToValue[string(bundleName)] = val
   439  		}
   440  		var sb strings.Builder
   441  		first := true
   442  		for _, bundleName := range bundleNames {
   443  			if val, ok := bundleNameToValue[string(bundleName)]; ok {
   444  				if !first {
   445  					sb.WriteString(", ")
   446  				}
   447  				sb.WriteString(fmt.Sprintf("bundle %q sets --%s=%q", bundleName, flagName, val))
   448  				first = false
   449  			}
   450  		}
   451  		return fmt.Errorf("flag --%s is specified by multiple bundles: %s", flagName, sb.String())
   452  	}
   453  
   454  	// Actually apply flag values.
   455  	for flagName, valueToBundleName := range flagToValueToBundleName {
   456  		fl := flagSet.Lookup(flagName)
   457  		if fl == nil {
   458  			return fmt.Errorf("flag --%s not found", flagName)
   459  		}
   460  		prevValue := fl.Value.String()
   461  		// Note: We verified earlier that valueToBundleName has length 1,
   462  		// so this loop executes exactly once per flag.
   463  		for val, bundleName := range valueToBundleName {
   464  			if prevValue == val {
   465  				continue
   466  			}
   467  			if isFlagExplicitlySet(flagSet, flagName) {
   468  				log.Infof("Flag --%s has explicitly-set value %q, but bundle %s takes precedence and is overriding its value to --%s=%q.", flagName, prevValue, bundleName, flagName, val)
   469  			} else {
   470  				log.Infof("Overriding flag --%s=%q from applying bundle %s.", flagName, val, bundleName)
   471  			}
   472  			if err := c.Override(flagSet, flagName, val /* force= */, true); err != nil {
   473  				return err
   474  			}
   475  		}
   476  	}
   477  
   478  	return c.validate()
   479  }
   480  
   481  func getVal(field reflect.Value) string {
   482  	if str, ok := field.Addr().Interface().(fmt.Stringer); ok {
   483  		return str.String()
   484  	}
   485  	switch field.Kind() {
   486  	case reflect.Bool:
   487  		return strconv.FormatBool(field.Bool())
   488  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   489  		return strconv.FormatInt(field.Int(), 10)
   490  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   491  		return strconv.FormatUint(field.Uint(), 10)
   492  	case reflect.String:
   493  		return field.String()
   494  	default:
   495  		panic("unknown type " + field.Kind().String())
   496  	}
   497  }