github.com/intel-go/viper@v1.5.1-0.20191113170345-18a46e0e5e35/README.md (about) 1 ![viper logo](https://cloud.githubusercontent.com/assets/173412/10886745/998df88a-8151-11e5-9448-4736db51020d.png) 2 3 Go configuration with fangs! 4 5 Many Go projects are built using Viper including: 6 7 * [Hugo](http://gohugo.io) 8 * [EMC RexRay](http://rexray.readthedocs.org/en/stable/) 9 * [Imgur’s Incus](https://github.com/Imgur/incus) 10 * [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) 11 * [Docker Notary](https://github.com/docker/Notary) 12 * [BloomApi](https://www.bloomapi.com/) 13 * [doctl](https://github.com/digitalocean/doctl) 14 * [Clairctl](https://github.com/jgsqware/clairctl) 15 * [Mercure](https://mercure.rocks) 16 17 [![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper) [![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/spf13/viper?status.svg)](https://godoc.org/github.com/spf13/viper) 18 19 ## Install 20 ```console 21 go get -u github.com/spf13/viper 22 ``` 23 24 ## What is Viper? 25 26 Viper is a complete configuration solution for Go applications including 12-Factor apps. It is designed 27 to work within an application, and can handle all types of configuration needs 28 and formats. It supports: 29 30 * setting defaults 31 * reading from JSON, TOML, YAML, HCL, envfile and Java properties config files 32 * live watching and re-reading of config files (optional) 33 * reading from environment variables 34 * reading from remote config systems (etcd or Consul), and watching changes 35 * reading from command line flags 36 * reading from buffer 37 * setting explicit values 38 39 Viper can be thought of as a registry for all of your applications 40 configuration needs. 41 42 ## Why Viper? 43 44 When building a modern application, you don’t want to worry about 45 configuration file formats; you want to focus on building awesome software. 46 Viper is here to help with that. 47 48 Viper does the following for you: 49 50 1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, envfile or Java properties formats. 51 2. Provide a mechanism to set default values for your different 52 configuration options. 53 3. Provide a mechanism to set override values for options specified through 54 command line flags. 55 4. Provide an alias system to easily rename parameters without breaking existing 56 code. 57 5. Make it easy to tell the difference between when a user has provided a 58 command line or config file which is the same as the default. 59 60 Viper uses the following precedence order. Each item takes precedence over the 61 item below it: 62 63 * explicit call to Set 64 * flag 65 * env 66 * config 67 * key/value store 68 * default 69 70 Viper configuration keys are case insensitive by default. They can be made case 71 sensitive with `viper.SetKeyCaseSensitivity(true)`. 72 73 ## Putting Values into Viper 74 75 ### Establishing Defaults 76 77 A good configuration system will support default values. A default value is not 78 required for a key, but it’s useful in the event that a key hasn’t been set via 79 config file, environment variable, remote configuration or flag. 80 81 Examples: 82 83 ```go 84 viper.SetDefault("ContentDir", "content") 85 viper.SetDefault("LayoutDir", "layouts") 86 viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"}) 87 ``` 88 89 ### Reading Config Files 90 91 Viper requires minimal configuration so it knows where to look for config files. 92 Viper supports JSON, TOML, YAML, HCL, envfile and Java Properties files. Viper can search multiple paths, but 93 currently a single Viper instance only supports a single configuration file. 94 Viper does not default to any configuration search paths leaving defaults decision 95 to an application. 96 97 Here is an example of how to use Viper to search for and read a configuration file. 98 None of the specific paths are required, but at least one path should be provided 99 where a configuration file is expected. 100 101 ```go 102 viper.SetConfigName("config") // name of config file (without extension) 103 viper.AddConfigPath("/etc/appname/") // path to look for the config file in 104 viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths 105 viper.AddConfigPath(".") // optionally look for config in the working directory 106 err := viper.ReadInConfig() // Find and read the config file 107 if err != nil { // Handle errors reading the config file 108 panic(fmt.Errorf("Fatal error config file: %s \n", err)) 109 } 110 ``` 111 112 You can handle the specific case where no config file is found like this: 113 114 ```go 115 if err := viper.ReadInConfig(); err != nil { 116 if _, ok := err.(viper.ConfigFileNotFoundError); ok { 117 // Config file not found; ignore error if desired 118 } else { 119 // Config file was found but another error was produced 120 } 121 } 122 123 // Config file found and successfully parsed 124 ``` 125 126 *NOTE:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc` 127 128 ### Writing Config Files 129 130 Reading from config files is useful, but at times you want to store all modifications made at run time. 131 For that, a bunch of commands are available, each with its own purpose: 132 133 * WriteConfig - writes the current viper configuration to the predefined path, if exists. Errors if no predefined path. Will overwrite the current config file, if it exists. 134 * SafeWriteConfig - writes the current viper configuration to the predefined path. Errors if no predefined path. Will not overwrite the current config file, if it exists. 135 * WriteConfigAs - writes the current viper configuration to the given filepath. Will overwrite the given file, if it exists. 136 * SafeWriteConfigAs - writes the current viper configuration to the given filepath. Will not overwrite the given file, if it exists. 137 138 As a rule of the thumb, everything marked with safe won't overwrite any file, but just create if not existent, whilst the default behavior is to create or truncate. 139 140 A small examples section: 141 142 ```go 143 viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName' 144 viper.SafeWriteConfig() 145 viper.WriteConfigAs("/path/to/my/.config") 146 viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written 147 viper.SafeWriteConfigAs("/path/to/my/.other_config") 148 ``` 149 150 ### Watching and re-reading config files 151 152 Viper supports the ability to have your application live read a config file while running. 153 154 Gone are the days of needing to restart a server to have a config take effect, 155 viper powered applications can read an update to a config file while running and 156 not miss a beat. 157 158 Simply tell the viper instance to watchConfig. 159 Optionally you can provide a function for Viper to run each time a change occurs. 160 161 **Make sure you add all of the configPaths prior to calling `WatchConfig()`** 162 163 ```go 164 viper.WatchConfig() 165 viper.OnConfigChange(func(e fsnotify.Event) { 166 fmt.Println("Config file changed:", e.Name) 167 }) 168 ``` 169 170 ### Reading Config from io.Reader 171 172 Viper predefines many configuration sources such as files, environment 173 variables, flags, and remote K/V store, but you are not bound to them. You can 174 also implement your own required configuration source and feed it to viper. 175 176 ```go 177 viper.SetConfigType("yaml") // or viper.SetConfigType("YAML") 178 179 // any approach to require this configuration into your program. 180 var yamlExample = []byte(` 181 Hacker: true 182 name: steve 183 hobbies: 184 - skateboarding 185 - snowboarding 186 - go 187 clothing: 188 jacket: leather 189 trousers: denim 190 age: 35 191 eyes : brown 192 beard: true 193 `) 194 195 viper.ReadConfig(bytes.NewBuffer(yamlExample)) 196 197 viper.Get("name") // this would be "steve" 198 ``` 199 200 ### Setting Overrides 201 202 These could be from a command line flag, or from your own application logic. 203 204 ```go 205 viper.Set("Verbose", true) 206 viper.Set("LogFile", LogFile) 207 ``` 208 209 ### Registering and Using Aliases 210 211 Aliases permit a single value to be referenced by multiple keys 212 213 ```go 214 viper.RegisterAlias("loud", "Verbose") 215 216 viper.Set("verbose", true) // same result as next line 217 viper.Set("loud", true) // same result as prior line 218 219 viper.GetBool("loud") // true 220 viper.GetBool("verbose") // true 221 ``` 222 223 ### Working with Environment Variables 224 225 Viper has full support for environment variables. This enables 12 factor 226 applications out of the box. There are five methods that exist to aid working 227 with ENV: 228 229 * `AutomaticEnv()` 230 * `BindEnv(string...) : error` 231 * `SetEnvPrefix(string)` 232 * `SetEnvKeyReplacer(string...) *strings.Replacer` 233 * `AllowEmptyEnv(bool)` 234 235 _When working with ENV variables, it’s important to recognize that Viper 236 treats ENV variables as case sensitive._ 237 238 Viper provides a mechanism to try to ensure that ENV variables are unique. By 239 using `SetEnvPrefix`, you can tell Viper to use a prefix while reading from 240 the environment variables. Both `BindEnv` and `AutomaticEnv` will use this 241 prefix. 242 243 `BindEnv` takes one or two parameters. The first parameter is the key name, the 244 second is the name of the environment variable. The name of the environment 245 variable is case sensitive. If the ENV variable name is not provided, then 246 Viper will automatically assume that the ENV variable matches the following format: prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV variable name (the second parameter), 247 it **does not** automatically add the prefix. For example if the second parameter is "id", 248 Viper will look for the ENV variable "ID". 249 250 One important thing to recognize when working with ENV variables is that the 251 value will be read each time it is accessed. Viper does not fix the value when 252 the `BindEnv` is called. 253 254 `AutomaticEnv` is a powerful helper especially when combined with 255 `SetEnvPrefix`. When called, Viper will check for an environment variable any 256 time a `viper.Get` request is made. It will apply the following rules. It will 257 check for a environment variable with a name matching the key uppercased and 258 prefixed with the `EnvPrefix` if set. 259 260 `SetEnvKeyReplacer` allows you to use a `strings.Replacer` object to rewrite Env 261 keys to an extent. This is useful if you want to use `-` or something in your 262 `Get()` calls, but want your environmental variables to use `_` delimiters. An 263 example of using it can be found in `viper_test.go`. 264 265 By default empty environment variables are considered unset and will fall back to 266 the next configuration source. To treat empty environment variables as set, use 267 the `AllowEmptyEnv` method. 268 269 #### Env example 270 271 ```go 272 SetEnvPrefix("spf") // will be uppercased automatically 273 BindEnv("id") 274 275 os.Setenv("SPF_ID", "13") // typically done outside of the app 276 277 id := Get("id") // 13 278 ``` 279 280 ### Working with Flags 281 282 Viper has the ability to bind to flags. Specifically, Viper supports `Pflags` 283 as used in the [Cobra](https://github.com/spf13/cobra) library. 284 285 Like `BindEnv`, the value is not set when the binding method is called, but when 286 it is accessed. This means you can bind as early as you want, even in an 287 `init()` function. 288 289 For individual flags, the `BindPFlag()` method provides this functionality. 290 291 Example: 292 293 ```go 294 serverCmd.Flags().Int("port", 1138, "Port to run Application server on") 295 viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) 296 ``` 297 298 You can also bind an existing set of pflags (pflag.FlagSet): 299 300 Example: 301 302 ```go 303 pflag.Int("flagname", 1234, "help message for flagname") 304 305 pflag.Parse() 306 viper.BindPFlags(pflag.CommandLine) 307 308 i := viper.GetInt("flagname") // retrieve values from viper instead of pflag 309 ``` 310 311 The use of [pflag](https://github.com/spf13/pflag/) in Viper does not preclude 312 the use of other packages that use the [flag](https://golang.org/pkg/flag/) 313 package from the standard library. The pflag package can handle the flags 314 defined for the flag package by importing these flags. This is accomplished 315 by a calling a convenience function provided by the pflag package called 316 AddGoFlagSet(). 317 318 Example: 319 320 ```go 321 package main 322 323 import ( 324 "flag" 325 "github.com/spf13/pflag" 326 ) 327 328 func main() { 329 330 // using standard library "flag" package 331 flag.Int("flagname", 1234, "help message for flagname") 332 333 pflag.CommandLine.AddGoFlagSet(flag.CommandLine) 334 pflag.Parse() 335 viper.BindPFlags(pflag.CommandLine) 336 337 i := viper.GetInt("flagname") // retrieve value from viper 338 339 ... 340 } 341 ``` 342 343 #### Flag interfaces 344 345 Viper provides two Go interfaces to bind other flag systems if you don’t use `Pflags`. 346 347 `FlagValue` represents a single flag. This is a very simple example on how to implement this interface: 348 349 ```go 350 type myFlag struct {} 351 func (f myFlag) HasChanged() bool { return false } 352 func (f myFlag) Name() string { return "my-flag-name" } 353 func (f myFlag) ValueString() string { return "my-flag-value" } 354 func (f myFlag) ValueType() string { return "string" } 355 ``` 356 357 Once your flag implements this interface, you can simply tell Viper to bind it: 358 359 ```go 360 viper.BindFlagValue("my-flag-name", myFlag{}) 361 ``` 362 363 `FlagValueSet` represents a group of flags. This is a very simple example on how to implement this interface: 364 365 ```go 366 type myFlagSet struct { 367 flags []myFlag 368 } 369 370 func (f myFlagSet) VisitAll(fn func(FlagValue)) { 371 for _, flag := range flags { 372 fn(flag) 373 } 374 } 375 ``` 376 377 Once your flag set implements this interface, you can simply tell Viper to bind it: 378 379 ```go 380 fSet := myFlagSet{ 381 flags: []myFlag{myFlag{}, myFlag{}}, 382 } 383 viper.BindFlagValues("my-flags", fSet) 384 ``` 385 386 ### Remote Key/Value Store Support 387 388 To enable remote support in Viper, do a blank import of the `viper/remote` 389 package: 390 391 `import _ "github.com/spf13/viper/remote"` 392 393 Viper will read a config string (as JSON, TOML, YAML, HCL or envfile) retrieved from a path 394 in a Key/Value store such as etcd or Consul. These values take precedence over 395 default values, but are overridden by configuration values retrieved from disk, 396 flags, or environment variables. 397 398 Viper uses [crypt](https://github.com/xordataexchange/crypt) to retrieve 399 configuration from the K/V store, which means that you can store your 400 configuration values encrypted and have them automatically decrypted if you have 401 the correct gpg keyring. Encryption is optional. 402 403 You can use remote configuration in conjunction with local configuration, or 404 independently of it. 405 406 `crypt` has a command-line helper that you can use to put configurations in your 407 K/V store. `crypt` defaults to etcd on http://127.0.0.1:4001. 408 409 ```bash 410 $ go get github.com/xordataexchange/crypt/bin/crypt 411 $ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json 412 ``` 413 414 Confirm that your value was set: 415 416 ```bash 417 $ crypt get -plaintext /config/hugo.json 418 ``` 419 420 See the `crypt` documentation for examples of how to set encrypted values, or 421 how to use Consul. 422 423 ### Remote Key/Value Store Example - Unencrypted 424 425 #### etcd 426 ```go 427 viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") 428 viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" 429 err := viper.ReadRemoteConfig() 430 ``` 431 432 #### Consul 433 You need to set a key to Consul key/value storage with JSON value containing your desired config. 434 For example, create a Consul key/value store key `MY_CONSUL_KEY` with value: 435 436 ```json 437 { 438 "port": 8080, 439 "hostname": "myhostname.com" 440 } 441 ``` 442 443 ```go 444 viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") 445 viper.SetConfigType("json") // Need to explicitly set this to json 446 err := viper.ReadRemoteConfig() 447 448 fmt.Println(viper.Get("port")) // 8080 449 fmt.Println(viper.Get("hostname")) // myhostname.com 450 ``` 451 452 ### Remote Key/Value Store Example - Encrypted 453 454 ```go 455 viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") 456 viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" 457 err := viper.ReadRemoteConfig() 458 ``` 459 460 ### Watching Changes in etcd - Unencrypted 461 462 ```go 463 // alternatively, you can create a new viper instance. 464 var runtime_viper = viper.New() 465 466 runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") 467 runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" 468 469 // read from remote config the first time. 470 err := runtime_viper.ReadRemoteConfig() 471 472 // unmarshal config 473 runtime_viper.Unmarshal(&runtime_conf) 474 475 // open a goroutine to watch remote changes forever 476 go func(){ 477 for { 478 time.Sleep(time.Second * 5) // delay after each request 479 480 // currently, only tested with etcd support 481 err := runtime_viper.WatchRemoteConfig() 482 if err != nil { 483 log.Errorf("unable to read remote config: %v", err) 484 continue 485 } 486 487 // unmarshal new config into our runtime config struct. you can also use channel 488 // to implement a signal to notify the system of the changes 489 runtime_viper.Unmarshal(&runtime_conf) 490 } 491 }() 492 ``` 493 494 ## Getting Values From Viper 495 496 In Viper, there are a few ways to get a value depending on the value’s type. 497 The following functions and methods exist: 498 499 * `Get(key string) : interface{}` 500 * `GetBool(key string) : bool` 501 * `GetFloat64(key string) : float64` 502 * `GetInt(key string) : int` 503 * `GetIntSlice(key string) : []int` 504 * `GetString(key string) : string` 505 * `GetStringMap(key string) : map[string]interface{}` 506 * `GetStringMapString(key string) : map[string]string` 507 * `GetStringSlice(key string) : []string` 508 * `GetTime(key string) : time.Time` 509 * `GetDuration(key string) : time.Duration` 510 * `IsSet(key string) : bool` 511 * `AllSettings() : map[string]interface{}` 512 513 One important thing to recognize is that each Get function will return a zero 514 value if it’s not found. To check if a given key exists, the `IsSet()` method 515 has been provided. 516 517 Example: 518 ```go 519 viper.GetString("logfile") // case-insensitive Setting & Getting 520 if viper.GetBool("verbose") { 521 fmt.Println("verbose enabled") 522 } 523 ``` 524 ### Accessing nested keys 525 526 The accessor methods also accept formatted paths to deeply nested keys. For 527 example, if the following JSON file is loaded: 528 529 ```json 530 { 531 "host": { 532 "address": "localhost", 533 "port": 5799 534 }, 535 "datastore": { 536 "metric": { 537 "host": "127.0.0.1", 538 "port": 3099 539 }, 540 "warehouse": { 541 "host": "198.0.0.1", 542 "port": 2112 543 } 544 } 545 } 546 547 ``` 548 549 Viper can access a nested field by passing a `.` delimited path of keys: 550 551 ```go 552 GetString("datastore.metric.host") // (returns "127.0.0.1") 553 ``` 554 555 This obeys the precedence rules established above; the search for the path 556 will cascade through the remaining configuration registries until found. 557 558 For example, given this configuration file, both `datastore.metric.host` and 559 `datastore.metric.port` are already defined (and may be overridden). If in addition 560 `datastore.metric.protocol` was defined in the defaults, Viper would also find it. 561 562 However, if `datastore.metric` was overridden (by a flag, an environment variable, 563 the `Set()` method, …) with an immediate value, then all sub-keys of 564 `datastore.metric` become undefined, they are “shadowed” by the higher-priority 565 configuration level. 566 567 Lastly, if there exists a key that matches the delimited key path, its value 568 will be returned instead. E.g. 569 570 ```json 571 { 572 "datastore.metric.host": "0.0.0.0", 573 "host": { 574 "address": "localhost", 575 "port": 5799 576 }, 577 "datastore": { 578 "metric": { 579 "host": "127.0.0.1", 580 "port": 3099 581 }, 582 "warehouse": { 583 "host": "198.0.0.1", 584 "port": 2112 585 } 586 } 587 } 588 589 GetString("datastore.metric.host") // returns "0.0.0.0" 590 ``` 591 592 ### Extract sub-tree 593 594 Extract sub-tree from Viper. 595 596 For example, `viper` represents: 597 598 ```json 599 app: 600 cache1: 601 max-items: 100 602 item-size: 64 603 cache2: 604 max-items: 200 605 item-size: 80 606 ``` 607 608 After executing: 609 610 ```go 611 subv := viper.Sub("app.cache1") 612 ``` 613 614 `subv` represents: 615 616 ```json 617 max-items: 100 618 item-size: 64 619 ``` 620 621 Suppose we have: 622 623 ```go 624 func NewCache(cfg *Viper) *Cache {...} 625 ``` 626 627 which creates a cache based on config information formatted as `subv`. 628 Now it’s easy to create these 2 caches separately as: 629 630 ```go 631 cfg1 := viper.Sub("app.cache1") 632 cache1 := NewCache(cfg1) 633 634 cfg2 := viper.Sub("app.cache2") 635 cache2 := NewCache(cfg2) 636 ``` 637 638 ### Unmarshaling 639 640 You also have the option of Unmarshaling all or a specific value to a struct, map, 641 etc. 642 643 There are two methods to do this: 644 645 * `Unmarshal(rawVal interface{}) : error` 646 * `UnmarshalKey(key string, rawVal interface{}) : error` 647 648 Example: 649 650 ```go 651 type config struct { 652 Port int 653 Name string 654 PathMap string `mapstructure:"path_map"` 655 } 656 657 var C config 658 659 err := viper.Unmarshal(&C) 660 if err != nil { 661 t.Fatalf("unable to decode into struct, %v", err) 662 } 663 ``` 664 665 Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default. 666 667 ### Marshalling to string 668 669 You may need to marshal all the settings held in viper into a string rather than write them to a file. 670 You can use your favorite format's marshaller with the config returned by `AllSettings()`. 671 672 ```go 673 import ( 674 yaml "gopkg.in/yaml.v2" 675 // ... 676 ) 677 678 func yamlStringSettings() string { 679 c := viper.AllSettings() 680 bs, err := yaml.Marshal(c) 681 if err != nil { 682 log.Fatalf("unable to marshal config to YAML: %v", err) 683 } 684 return string(bs) 685 } 686 ``` 687 688 ## Viper or Vipers? 689 690 Viper comes ready to use out of the box. There is no configuration or 691 initialization needed to begin using Viper. Since most applications will want 692 to use a single central repository for their configuration, the viper package 693 provides this. It is similar to a singleton. 694 695 In all of the examples above, they demonstrate using viper in its singleton 696 style approach. 697 698 ### Working with multiple vipers 699 700 You can also create many different vipers for use in your application. Each will 701 have its own unique set of configurations and values. Each can read from a 702 different config file, key value store, etc. All of the functions that viper 703 package supports are mirrored as methods on a viper. 704 705 Example: 706 707 ```go 708 x := viper.New() 709 y := viper.New() 710 711 x.SetDefault("ContentDir", "content") 712 y.SetDefault("ContentDir", "foobar") 713 714 //... 715 ``` 716 717 When working with multiple vipers, it is up to the user to keep track of the 718 different vipers. 719 720 ## Q & A 721 722 Q: Why not INI files? 723 724 A: Ini files are pretty awful. There’s no standard format, and they are hard to 725 validate. Viper is designed to work with JSON, TOML or YAML files. If someone 726 really wants to add this feature, I’d be happy to merge it. It’s easy to specify 727 which formats your application will permit. 728 729 Q: Why is it called “Viper”? 730 731 A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe)) 732 to [Cobra](https://github.com/spf13/cobra). While both can operate completely 733 independently, together they make a powerful pair to handle much of your 734 application foundation needs. 735 736 Q: Why is it called “Cobra”? 737 738 A: Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)?