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