gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/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  	"gvisor.dev/gvisor/pkg/log"
    29  	"gvisor.dev/gvisor/pkg/refs"
    30  	"gvisor.dev/gvisor/pkg/sentry/watchdog"
    31  	"gvisor.dev/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; use the special value '-' to write to the user-visible logs. (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.Bool("gvisor-gro", false, "enable gVisor generic receive offload")
   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.Int("network-processors-per-channel", 0, "number of goroutines in each channel for processng inbound packets. If 0, the link endpoint will divide GOMAXPROCS evenly among the number of channels specified by num-network-channels.")
   123  	flagSet.Bool("buffer-pooling", true, "DEPRECATED: this flag has no effect. Buffer pooling is always enabled.")
   124  	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>"`)
   125  	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.
   126  	flagSet.Bool("reproduce-nat", false, "Scrape the host netns NAT table and reproduce it in the sandbox.")
   127  	flagSet.Bool("reproduce-nftables", false, "Attempt to scrape and reproduce nftable rules inside the sandbox. Overrides reproduce-nat when true.")
   128  	flagSet.Bool("net-disconnect-ok", false, "Indicates whether the link endpoint capability CapabilityDisconnectOk should be set. This allows open connections to be disconnected upon save.")
   129  
   130  	// Flags that control sandbox runtime behavior: accelerator related.
   131  	flagSet.Bool("nvproxy", false, "EXPERIMENTAL: enable support for Nvidia GPUs")
   132  	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.")
   133  	flagSet.String("nvproxy-driver-version", "", "NVIDIA driver ABI version to use. If empty, autodetect installed driver version. The special value 'latest' may also be used to use the latest ABI.")
   134  	flagSet.Bool("tpuproxy", false, "EXPERIMENTAL: enable support for TPU device passthrough.")
   135  
   136  	// Test flags, not to be used outside tests, ever.
   137  	flagSet.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.")
   138  	flagSet.String("TESTONLY-test-name-env", "", "TEST ONLY; do not ever use! Used for automated tests to improve logging.")
   139  	flagSet.Bool("TESTONLY-allow-packet-endpoint-write", false, "TEST ONLY; do not ever use! Used for tests to allow writes on packet sockets.")
   140  	flagSet.Bool("TESTONLY-afs-syscall-panic", false, "TEST ONLY; do not ever use! Used for tests exercising gVisor panic reporting.")
   141  	flagSet.String("TESTONLY-autosave-image-path", "", "TEST ONLY; enable auto save for syscall tests and set path for state file.")
   142  	flagSet.Bool("TESTONLY-autosave-resume", false, "TEST ONLY; enable auto save and resume for syscall tests and set path for state file.")
   143  }
   144  
   145  // overrideAllowlist lists all flags that can be changed using OCI
   146  // annotations without an administrator setting `--allow-flag-override` on the
   147  // runtime. Flags in this list can be set by container authors and should not
   148  // make the sandbox less secure.
   149  var overrideAllowlist = map[string]struct {
   150  	check func(name string, value string) error
   151  }{
   152  	"debug":             {},
   153  	"debug-to-user-log": {},
   154  	"strace":            {},
   155  	"strace-syscalls":   {},
   156  	"strace-log-size":   {},
   157  	"host-uds":          {},
   158  	"net-disconnect-ok": {},
   159  
   160  	"oci-seccomp": {check: checkOciSeccomp},
   161  }
   162  
   163  // checkOciSeccomp ensures that seccomp can be enabled but not disabled.
   164  func checkOciSeccomp(name string, value string) error {
   165  	enable, err := strconv.ParseBool(value)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	if !enable {
   170  		return fmt.Errorf("disabling %q requires flag %q to be enabled", name, "allow-flag-override")
   171  	}
   172  	return nil
   173  }
   174  
   175  // isFlagExplicitlySet returns whether the given flag name is explicitly set.
   176  // Doesn't check for flag existence; returns `false` for flags that don't exist.
   177  func isFlagExplicitlySet(flagSet *flag.FlagSet, name string) bool {
   178  	explicit := false
   179  
   180  	// The FlagSet.Visit function only visits flags that are explicitly set, as opposed to VisitAll.
   181  	flagSet.Visit(func(fl *flag.Flag) {
   182  		explicit = explicit || fl.Name == name
   183  	})
   184  
   185  	return explicit
   186  }
   187  
   188  // NewFromFlags creates a new Config with values coming from command line flags.
   189  func NewFromFlags(flagSet *flag.FlagSet) (*Config, error) {
   190  	conf := &Config{explicitlySet: map[string]struct{}{}}
   191  
   192  	obj := reflect.ValueOf(conf).Elem()
   193  	st := obj.Type()
   194  	for i := 0; i < st.NumField(); i++ {
   195  		f := st.Field(i)
   196  		name, ok := f.Tag.Lookup("flag")
   197  		if !ok {
   198  			// No flag set for this field.
   199  			continue
   200  		}
   201  		fl := flagSet.Lookup(name)
   202  		if fl == nil {
   203  			panic(fmt.Sprintf("Flag %q not found", name))
   204  		}
   205  		x := reflect.ValueOf(flag.Get(fl.Value))
   206  		obj.Field(i).Set(x)
   207  		if isFlagExplicitlySet(flagSet, name) {
   208  			conf.explicitlySet[name] = struct{}{}
   209  		}
   210  	}
   211  
   212  	if len(conf.RootDir) == 0 {
   213  		// If not set, set default root dir to something (hopefully) user-writeable.
   214  		conf.RootDir = "/var/run/runsc"
   215  		// NOTE: empty values for XDG_RUNTIME_DIR should be ignored.
   216  		if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
   217  			conf.RootDir = filepath.Join(runtimeDir, "runsc")
   218  		}
   219  	}
   220  
   221  	if err := conf.validate(); err != nil {
   222  		return nil, err
   223  	}
   224  	return conf, nil
   225  }
   226  
   227  // NewFromBundle makes a new config from a Bundle.
   228  func NewFromBundle(bundle Bundle) (*Config, error) {
   229  	if err := bundle.Validate(); err != nil {
   230  		return nil, err
   231  	}
   232  	flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError)
   233  	RegisterFlags(flagSet)
   234  	conf := &Config{explicitlySet: map[string]struct{}{}}
   235  
   236  	obj := reflect.ValueOf(conf).Elem()
   237  	st := obj.Type()
   238  	for i := 0; i < st.NumField(); i++ {
   239  		f := st.Field(i)
   240  		name, ok := f.Tag.Lookup("flag")
   241  		if !ok {
   242  			continue
   243  		}
   244  		fl := flagSet.Lookup(name)
   245  		if fl == nil {
   246  			return nil, fmt.Errorf("flag %q not found", name)
   247  		}
   248  		val, ok := bundle[name]
   249  		if !ok {
   250  			continue
   251  		}
   252  		if err := flagSet.Set(name, val); err != nil {
   253  			return nil, fmt.Errorf("error setting flag %s=%q: %w", name, val, err)
   254  		}
   255  		conf.Override(flagSet, name, val, true)
   256  
   257  		conf.explicitlySet[name] = struct{}{}
   258  	}
   259  	return conf, nil
   260  }
   261  
   262  // ToFlags returns a slice of flags that correspond to the given Config.
   263  func (c *Config) ToFlags() []string {
   264  	flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError)
   265  	RegisterFlags(flagSet)
   266  
   267  	var rv []string
   268  	keyVals := c.keyVals(flagSet, false /*onlyIfSet*/)
   269  	for name, val := range keyVals {
   270  		rv = append(rv, fmt.Sprintf("--%s=%s", name, val))
   271  	}
   272  
   273  	// Construct a temporary set for default plumbing.
   274  	return rv
   275  }
   276  
   277  // KeyVal is a key value pair. It is used so ToContainerdConfigTOML returns
   278  // predictable ordering for runsc flags.
   279  type KeyVal struct {
   280  	Key string
   281  	Val string
   282  }
   283  
   284  // ContainerdConfigOptions contains arguments for ToContainerdConfigTOML.
   285  type ContainerdConfigOptions struct {
   286  	BinaryPath string
   287  	RootPath   string
   288  	Options    map[string]string
   289  	RunscFlags []KeyVal
   290  }
   291  
   292  // ToContainerdConfigTOML turns a given config into a format for a k8s containerd config.toml file.
   293  // See: https://gvisor.dev/docs/user_guide/containerd/quick_start/
   294  func (c *Config) ToContainerdConfigTOML(opts ContainerdConfigOptions) (string, error) {
   295  	flagSet := flag.NewFlagSet("tmp", flag.ContinueOnError)
   296  	RegisterFlags(flagSet)
   297  	keyVals := c.keyVals(flagSet, true /*onlyIfSet*/)
   298  	keys := []string{}
   299  	for k := range keyVals {
   300  		keys = append(keys, k)
   301  	}
   302  
   303  	sort.Strings(keys)
   304  
   305  	for _, k := range keys {
   306  		opts.RunscFlags = append(opts.RunscFlags, KeyVal{k, keyVals[k]})
   307  	}
   308  
   309  	const temp = `{{if .BinaryPath}}binary_name = "{{.BinaryPath}}"{{end}}
   310  {{if .RootPath}}root = "{{.RootPath}}"{{end}}
   311  {{if .Options}}{{ range $key, $value := .Options}}{{$key}} = "{{$value}}"
   312  {{end}}{{end}}{{if .RunscFlags}}[runsc_config]
   313  {{ range $fl:= .RunscFlags}}  {{$fl.Key}} = "{{$fl.Val}}"
   314  {{end}}{{end}}`
   315  
   316  	t := template.New("temp")
   317  	t, err := t.Parse(temp)
   318  	if err != nil {
   319  		return "", err
   320  	}
   321  	var buf bytes.Buffer
   322  	if err := t.Execute(&buf, opts); err != nil {
   323  		return "", err
   324  	}
   325  	return buf.String(), nil
   326  }
   327  
   328  func (c *Config) keyVals(flagSet *flag.FlagSet, onlyIfSet bool) map[string]string {
   329  	keyVals := make(map[string]string)
   330  
   331  	obj := reflect.ValueOf(c).Elem()
   332  	st := obj.Type()
   333  	for i := 0; i < st.NumField(); i++ {
   334  		f := st.Field(i)
   335  		name, ok := f.Tag.Lookup("flag")
   336  		if !ok {
   337  			// No flag set for this field.
   338  			continue
   339  		}
   340  		val := getVal(obj.Field(i))
   341  
   342  		fl := flagSet.Lookup(name)
   343  		if fl == nil {
   344  			panic(fmt.Sprintf("Flag %q not found", name))
   345  		}
   346  		if val == fl.DefValue || onlyIfSet {
   347  			// If this config wasn't populated from a FlagSet, don't plumb through default flags.
   348  			if c.explicitlySet == nil {
   349  				continue
   350  			}
   351  			// If this config was populated from a FlagSet, plumb through only default flags which were
   352  			// explicitly specified.
   353  			if _, explicit := c.explicitlySet[name]; !explicit {
   354  				continue
   355  			}
   356  		}
   357  		keyVals[fl.Name] = val
   358  	}
   359  	return keyVals
   360  }
   361  
   362  // Override writes a new value to a flag.
   363  func (c *Config) Override(flagSet *flag.FlagSet, name string, value string, force bool) error {
   364  	obj := reflect.ValueOf(c).Elem()
   365  	st := obj.Type()
   366  	for i := 0; i < st.NumField(); i++ {
   367  		f := st.Field(i)
   368  		fieldName, ok := f.Tag.Lookup("flag")
   369  		if !ok || fieldName != name {
   370  			// Not a flag field, or flag name doesn't match.
   371  			continue
   372  		}
   373  		fl := flagSet.Lookup(name)
   374  		if fl == nil {
   375  			// Flag must exist if there is a field match above.
   376  			panic(fmt.Sprintf("Flag %q not found", name))
   377  		}
   378  		if !force {
   379  			if err := c.isOverrideAllowed(name, value); err != nil {
   380  				return fmt.Errorf("error setting flag %s=%q: %w", name, value, err)
   381  			}
   382  		}
   383  
   384  		// Use flag to convert the string value to the underlying flag type, using
   385  		// the same rules as the command-line for consistency.
   386  		if err := fl.Value.Set(value); err != nil {
   387  			return fmt.Errorf("error setting flag %s=%q: %w", name, value, err)
   388  		}
   389  		x := reflect.ValueOf(flag.Get(fl.Value))
   390  		obj.Field(i).Set(x)
   391  
   392  		// Validates the config again to ensure it's left in a consistent state.
   393  		return c.validate()
   394  	}
   395  	return fmt.Errorf("flag %q not found. Cannot set it to %q", name, value)
   396  }
   397  
   398  func (c *Config) isOverrideAllowed(name string, value string) error {
   399  	if c.AllowFlagOverride {
   400  		return nil
   401  	}
   402  	// If the global override flag is not enabled, check if the individual flag is
   403  	// safe to apply.
   404  	if allow, ok := overrideAllowlist[name]; ok {
   405  		if allow.check != nil {
   406  			if err := allow.check(name, value); err != nil {
   407  				return err
   408  			}
   409  		}
   410  		return nil
   411  	}
   412  	return fmt.Errorf("flag override disabled, use --allow-flag-override to enable it")
   413  }
   414  
   415  // ApplyBundles applies the given bundles by name.
   416  // It returns an error if a bundle doesn't exist, or if the given
   417  // bundles have conflicting flag values.
   418  // Config values which are already specified prior to calling ApplyBundles are overridden.
   419  func (c *Config) ApplyBundles(flagSet *flag.FlagSet, bundleNames ...BundleName) error {
   420  	// Populate a map from flag name to flag value to bundle name.
   421  	flagToValueToBundleName := make(map[string]map[string]BundleName)
   422  	for _, bundleName := range bundleNames {
   423  		b := Bundles[bundleName]
   424  		if b == nil {
   425  			return fmt.Errorf("no such bundle: %q", bundleName)
   426  		}
   427  		for flagName, val := range b {
   428  			valueToBundleName := flagToValueToBundleName[flagName]
   429  			if valueToBundleName == nil {
   430  				valueToBundleName = make(map[string]BundleName)
   431  				flagToValueToBundleName[flagName] = valueToBundleName
   432  			}
   433  			valueToBundleName[val] = bundleName
   434  		}
   435  	}
   436  	// Check for conflicting flag values between the bundles.
   437  	for flagName, valueToBundleName := range flagToValueToBundleName {
   438  		if len(valueToBundleName) == 1 {
   439  			continue
   440  		}
   441  		bundleNameToValue := make(map[string]string)
   442  		for val, bundleName := range valueToBundleName {
   443  			bundleNameToValue[string(bundleName)] = val
   444  		}
   445  		var sb strings.Builder
   446  		first := true
   447  		for _, bundleName := range bundleNames {
   448  			if val, ok := bundleNameToValue[string(bundleName)]; ok {
   449  				if !first {
   450  					sb.WriteString(", ")
   451  				}
   452  				sb.WriteString(fmt.Sprintf("bundle %q sets --%s=%q", bundleName, flagName, val))
   453  				first = false
   454  			}
   455  		}
   456  		return fmt.Errorf("flag --%s is specified by multiple bundles: %s", flagName, sb.String())
   457  	}
   458  
   459  	// Actually apply flag values.
   460  	for flagName, valueToBundleName := range flagToValueToBundleName {
   461  		fl := flagSet.Lookup(flagName)
   462  		if fl == nil {
   463  			return fmt.Errorf("flag --%s not found", flagName)
   464  		}
   465  		prevValue := fl.Value.String()
   466  		// Note: We verified earlier that valueToBundleName has length 1,
   467  		// so this loop executes exactly once per flag.
   468  		for val, bundleName := range valueToBundleName {
   469  			if prevValue == val {
   470  				continue
   471  			}
   472  			if isFlagExplicitlySet(flagSet, flagName) {
   473  				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)
   474  			} else {
   475  				log.Infof("Overriding flag --%s=%q from applying bundle %s.", flagName, val, bundleName)
   476  			}
   477  			if err := c.Override(flagSet, flagName, val /* force= */, true); err != nil {
   478  				return err
   479  			}
   480  		}
   481  	}
   482  
   483  	return c.validate()
   484  }
   485  
   486  func getVal(field reflect.Value) string {
   487  	if str, ok := field.Addr().Interface().(fmt.Stringer); ok {
   488  		return str.String()
   489  	}
   490  	switch field.Kind() {
   491  	case reflect.Bool:
   492  		return strconv.FormatBool(field.Bool())
   493  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   494  		return strconv.FormatInt(field.Int(), 10)
   495  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   496  		return strconv.FormatUint(field.Uint(), 10)
   497  	case reflect.String:
   498  		return field.String()
   499  	default:
   500  		panic("unknown type " + field.Kind().String())
   501  	}
   502  }