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 }