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