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