Can command line flags in Go be set to mandatory? - go

Is there a way how to set that certain flags are mandatory, or do I have to check for their presence on my own?

The flag package does not support mandatory or required flags (meaning the flag must be specified explicitly).
What you can do is use sensible default values for (all) flags. And if a flag is something like there is no sensible default, check the value at the start of your application and halt with an error message. You should do flag value validation anyway (not just for required flags), so this shouldn't mean any (big) overhead, and this is a good practice in general.

As already mentioned, the flag package does not provide this feature directly and usually you can (and should) be able to provide a sensible default. For cases where you only need a small number of explicit arguments (e.g. an input and output filename) you could use positional arguments (e.g. after flag.Parse() check that flag.NArg()==2 and then input, output := flag.Arg(0), flag.Arg(1)).
If however, you have a case where this isn't sensible; say a few integer flags you want to accept in any order, where any integer value is reasonable, but no default is. Then you can use the flag.Visit function to check if the flags you care about were explicitly set or not. I think this is the only way to tell if a flag was explicitly set to it's default value (not counting a custom flag.Value type with a Set implementation that kept state).
For example, perhaps something like:
required := []string{"b", "s"}
flag.Parse()
seen := make(map[string]bool)
flag.Visit(func(f *flag.Flag) { seen[f.Name] = true })
for _, req := range required {
if !seen[req] {
// or possibly use `log.Fatalf` instead of:
fmt.Fprintf(os.Stderr, "missing required -%s argument/flag\n", req)
os.Exit(2) // the same exit code flag.Parse uses
}
}
Playground
This would produce an error if either the "-b" or "-s" flag was not explicitly set.

go-flags lets you declare both required flags and required positional arguments:
var opts struct {
Flag string `short:"f" required:"true" name:"a flag"`
Args struct {
First string `positional-arg-name:"first arg"`
Sencond string `positional-arg-name:"second arg"`
} `positional-args:"true" required:"2"`
}
args, err := flags.Parse(&opts)

I like github.com/jessevdk/go-flags package to use in CLI. It provides a required attribute, to set a mandatory flag:
var opts struct {
...
// Example of a required flag
Name string `short:"n" long:"name" description:"A name" required:"true"`
...
}

If you have flag path, simply check if *path contains some value
var path = flag.String("f", "", "/path/to/access.log")
flag.Parse()
if *path == "" {
usage()
os.Exit(1)
}

I agree with this solution but, in my case default values are usually environment values. For example,
dsn := flag.String("dsn", os.Getenv("MYSQL_DSN"), "data source name")
And in this case, I want to check if the values are set from invocation (usually local development) or environment var (prod environment).
So with some minor modifications, it worked for my case.
Using flag.VisitAll to check the value of all flags.
required := []string{"b", "s"}
flag.Parse()
seen := make(map[string]bool)
flag.VisitAll(func(f *flag.Flag) {
if f.Value.String() != "" {
seen[f.Name] = true
}
})
for _, req := range required {
if !seen[req] {
// or possibly use `log.Fatalf` instead of:
fmt.Fprintf(os.Stderr, "missing required -%s argument/flag\n", req)
os.Exit(2) // the same exit code flag.Parse uses
}
}
Test example in plauground

Or you could docopt, where you only have to write the "usage" text. Docopt interprets the usage text and creates an argument map. This opens up a whole lot of possibilities, all following the POSIX usage text standard. This library is available for about 20 languages already.
https://github.com/docopt/docopt.go
package main
import (
"fmt"
"github.com/docopt/docopt-go"
)
const (
Usage = `Naval Fate.
Usage:
naval_fate ship new <name>...
naval_fate ship <name> move <x> <y> [--speed=<kn>]
naval_fate ship shoot <x> <y>
naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
naval_fate -h | --help
naval_fate --version
Options:
-h --help Show this screen.
--version Show version.
--speed=<kn> Speed in knots [default: 10].
--moored Moored (anchored) mine.
--drifting Drifting mine.`
)
func main() {
args, _ := docopt.ParseDoc(Usage)
fmt.Println(args)
}

A thing to note is that when you do:
password := flag.String("password", "", "the password")
flag.Parse()
The default is set by flag.String, not flag.Parse.
So if you instead do:
const unspecified = "\x00"
password := flag.String("password", "", "the password")
*password = unspecified
flag.Parse()
Then *password == unspecified if you don't specify it explicitly in the command line. This is my go to for strings when I want to distinguish "empty" from "unspecified".

Here's a full working example.
Work around the Usage and DefValue attributes, and wrap your flags into a struct.
package main
import (
"flag"
"fmt"
"os"
)
type CliFlag struct {
Required bool
Usage string
Name string
Address *string
}
func init() {
flags := [...]CliFlag{
{
Required: true,
Usage: "(github.com) repository URI",
Name: "repo-uri",
Address: nil,
},
{
Required: true,
Usage: "(Zombro) repository workspace",
Name: "repo-workspace",
Address: nil,
},
{
Required: true,
Usage: "(zzio) repository slug",
Name: "repo-slug",
Address: nil,
},
}
for i, f := range flags {
f.Address = flag.String(f.Name, "", f.Usage)
flags[i] = f
}
flag.Parse()
missing := make([]string, 0)
for _, f := range flags {
if *f.Address == "" && f.Required {
missing = append(missing, f.Name)
}
}
if len(missing) > 0 {
fmt.Printf("missing required flags: %v \n", missing)
flag.Usage()
os.Exit(1)
}
}
func main() {
fmt.Println("main")
}
missing
$ go run . -repo-slug test
missing required flags: [repo-uri repo-workspace]
Usage of /var/folders/mg/86n5kszs27bdqj0fpswvr0m00000gn/T/go-build2061541798/b001/exe/zzio:
-repo-slug string
(zzio) repository slug
-repo-uri string
(github.com) repository URI
-repo-workspace string
(Zombro) repository workspace
exit status 1

Related

Why am I getting a nil pointer error depending on where I call BindPFlag?

I've just recently started working with Go, and I've run into some
behavior working with Cobra and Viper that I'm not sure I understand.
This is a slightly modified version of the sample code you get by
running cobra init. In main.go I have:
package main
import (
"github.com/larsks/example/cmd"
"github.com/spf13/cobra"
)
func main() {
rootCmd := cmd.NewCmdRoot()
cobra.CheckErr(rootCmd.Execute())
}
In cmd/root.go I have:
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
func NewCmdRoot() *cobra.Command {
config := viper.New()
var cmd = &cobra.Command{
Use: "example",
Short: "A brief description of your application",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
initConfig(cmd, config)
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("This is a test\n")
},
}
cmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.example.yaml)")
cmd.PersistentFlags().String("name", "", "a name")
// *** If I move this to the top of initConfig
// *** the code runs correctly.
config.BindPFlag("name", cmd.Flags().Lookup("name"))
return cmd
}
func initConfig(cmd *cobra.Command, config *viper.Viper) {
if cfgFile != "" {
// Use config file from the flag.
config.SetConfigFile(cfgFile)
} else {
config.AddConfigPath(".")
config.SetConfigName(".example")
}
config.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := config.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "Using config file:", config.ConfigFileUsed())
}
// *** This line triggers a nil pointer reference.
fmt.Printf("name is %s\n", config.GetString("name"))
}
This code will panic with a nil pointer reference at the final call to
fmt.Printf:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x50 pc=0x6a90e5]
If I move the call to config.BindPFlag from the NewCmdRoot
function to the top of the initConfig command, everything runs
without a problem.
What's going on here? According to the Viper docs regarding the use of
BindPFlags:
Like BindEnv, the value is not set when the binding method is
called, but when it is accessed. This means you can bind as early as
you want, even in an init() function.
That's almost exactly what I'm doing here. At the time I call
config.BindPflag, config is non-nil, cmd is non-nil, and the
name argument has been registered.
I assume there's something going on with my use of config in a
closure in PersistentPreRun, but I don't know exactly why that is
causing this failure.
I thought this was interesting so I did some digging and found your exact problem documented in an issue. The problematic line is this:
config.BindPFlag("name", cmd.Flags().Lookup("name"))
// ^^^^^^^
You created a persistent flag, but bound the flag to the Flags property. If you change your code to bind to PersistentFlags, everything will work as intended even with this line in NewCmdRoot:
config.BindPFlag("name", cmd.PersistentFlags().Lookup("name"))
I don't have any issue if I use cmd.PersistentFlags().Lookup("name").
// *** If I move this to the top of initConfig
// *** the code runs correctly.
pflag := cmd.PersistentFlags().Lookup("name")
config.BindPFlag("name", pflag)
Considering you just registered persistent flags (flag will be available to the command it's assigned to as well as every command under that command), it is safer to call cmd.PersistentFlags().Lookup("name"), rather than cmd.Flags().Lookup("name").
The latter returns nil, since the PersistentPreRun is only called when rootCmd.Execute() is called, which is after cmd.NewCmdRoot().
At cmd.NewCmdRoot() levels, flags have not yet been initialized, even after some were declared "persistent".
This ends up being a little more complex than it might appear at first glance, so while the other answers here helped me resolve the problem I'd like to add a little detail.
There are some nuances in the documentation that aren't particularly clear if you're just starting to work with Cobra. Let's start with the documentation for the PersistentFlags method:
PersistentFlags returns the persistent FlagSet specifically set in the current command.
The key is in ...in the current command. In my NewCmdRoot root method, we can use cmd.PersistentFlags() because the root command is the current command. We can even use cmd.PersistentFlags() in the PersistentPreRun method, as long as we're not processing a subcommand.
If we were to re-write cmd/root.go from the example so that it includes a subcommand, like this...
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
func NewCmdSubcommand() *cobra.Command {
var cmd = &cobra.Command{
Use: "subcommand",
Short: "An example subcommand",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("This is an example subcommand\n")
},
}
return cmd
}
func NewCmdRoot() *cobra.Command {
config := viper.New()
var cmd = &cobra.Command{
Use: "example",
Short: "A brief description of your application",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
initConfig(cmd, config)
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Hello, world\n")
},
}
cmd.PersistentFlags().StringVar(
&cfgFile, "config", "", "config file (default is $HOME/.example.yaml)")
cmd.PersistentFlags().String("name", "", "a name")
cmd.AddCommand(NewCmdSubcommand())
err := config.BindPFlag("name", cmd.PersistentFlags().Lookup("name"))
if err != nil {
panic(err)
}
return cmd
}
func initConfig(cmd *cobra.Command, config *viper.Viper) {
name, err := cmd.PersistentFlags().GetString("name")
if err != nil {
panic(err)
}
fmt.Printf("name = %s\n", name)
if cfgFile != "" {
// Use config file from the flag.
config.SetConfigFile(cfgFile)
} else {
config.AddConfigPath(".")
config.SetConfigName(".example")
}
config.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := config.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "Using config file:", config.ConfigFileUsed())
}
// *** This line triggers a nil pointer reference.
fmt.Printf("name is %s\n", config.GetString("name"))
}
...we would find that it works when executing the root command:
$ ./example
name =
name is
Hello, world
But it fails when we run the subcommand:
[lars#madhatter go]$ ./example subcommand
panic: flag accessed but not defined: name
goroutine 1 [running]:
example/cmd.initConfig(0xc000172000, 0xc0001227e0)
/home/lars/tmp/go/cmd/root.go:55 +0x368
example/cmd.NewCmdRoot.func1(0xc000172000, 0x96eca0, 0x0, 0x0)
/home/lars/tmp/go/cmd/root.go:32 +0x34
github.com/spf13/cobra.(*Command).execute(0xc000172000, 0x96eca0, 0x0, 0x0, 0xc000172000, 0x96eca0)
/home/lars/go/pkg/mod/github.com/spf13/cobra#v1.1.3/command.go:836 +0x231
github.com/spf13/cobra.(*Command).ExecuteC(0xc00011db80, 0x0, 0xffffffff, 0xc0000240b8)
/home/lars/go/pkg/mod/github.com/spf13/cobra#v1.1.3/command.go:960 +0x375
github.com/spf13/cobra.(*Command).Execute(...)
/home/lars/go/pkg/mod/github.com/spf13/cobra#v1.1.3/command.go:897
main.main()
/home/lars/tmp/go/main.go:11 +0x2a
This is because the subcommand inherits the PersistentPreRun command from the root (this is what the Persistent part means), but when this method runs, the cmd argument passwd to PersistentPreRun is no longer the root command; it's the subcommand command. When we try to call cmd.PersistentFlags(), it fails because the current command doesn't have any persistent flags associated with it.
In this case, we need to instead use the Flags method:
Flags returns the complete FlagSet that applies to this command (local and persistent declared here and by all parents).
This gives us access to persistent flags declared by parents.
An additional issue, that doesn't appear to be called out explicitly in the documentation, is that Flags() is only available after command processing has been run (that is, after you call cmd.Execute() on the command or a parent). That means we can use it in PersistentPreRun, but we can't use it in NewCmdRoot (because that method finishes before we process the command line).
TL;DR
We have to use cmd.PersistentFlags() in NewCmdRoot because we're looking for persistent flags applied to the current command, and the value from Flags() won't be available yet.
We need to use cmd.Flags() in PersistentPreRun (and other persistent commands methods) because when processing a subcommand, PersistentFlags will only look for persistent flags on the current command, but won't traverse parents. We need to use cmd.Flags() instead, which will roll up persistent flags declared by parents.

How is a Cobra Flag of type `StringToStringVar` access using Viper?

I am trying to develop an application in Go that accepts inputs on the command line as a string of key value pairs. To do this I am using StrngToStringVar from the Cobra library.
I am also using Viper to bind these flags to configuration, however whatever value I put it I do not seem to be able to get it from Viper.
This is the code
package main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/davecgh/go-spew/spew"
)
func main() {
var items map[string]string
var name string
rootCmd := &cobra.Command{
Use: "cobra",
Short: "Test options on the command line",
Long: ``,
Run: func(ccmd *cobra.Command, args []string) {
spew.Dump(viper.GetString("name"))
spew.Println("")
fmt.Println("GetStringMap")
spew.Dump(viper.GetStringMap("items"))
fmt.Println("")
fmt.Println("GetStringMapString")
spew.Dump(viper.GetStringMapString("items"))
},
}
rootCmd.Flags().StringVar(&name, "name", "", "Name of the list")
rootCmd.Flags().StringToStringVar(&items, "item", nil, "Map stating the items to be included")
viper.BindPFlag("name", rootCmd.Flags().Lookup("name"))
viper.BindPFlag("items", rootCmd.Flags().Lookup("item"))
rootCmd.Execute()
}
If I run this with the command go run .\main.go --item shopping=apple,banana --name foobar I get the following result
(string) (len=6) "foobar"
GetStringMap
(map[string]interface {}) {
}
GetStringMapString
(map[string]string) {
}
As can be seent he output contains nothing for the items even though I set the input (I believe) correctly. I have been trying to use PR https://github.com/spf13/pflag/pull/133 to work out how to do it but I am not having any luck.
I am wondering if the binding is incorrect, but I have used CObra successfully in other projects so it is my lack of understanding as to how to reference the generated map[string]string from Cobra.
As far as the binding part is concerned, it is correct. However, before processing, you can verify if a particular key exists using vipver.IsSet(), print all the available keys using viper.AllKeys() or print everything using viper.AllSettings().
Here's an example (test.go):
package main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func main() {
var items map[string]string
rootCmd := &cobra.Command{
Use: "app",
Run: func(ccmd *cobra.Command, args []string) {
fmt.Println("item exists?", viper.IsSet("item"))
fmt.Println("GetString :", viper.GetString("item"))
fmt.Println("Keys :", viper.AllKeys())
fmt.Println("Settings :", viper.AllSettings())
},
}
rootCmd.Flags().StringToStringVarP(&items, "item", "i", nil, "Map stating the items to be included")
viper.BindPFlag("item", rootCmd.Flags().Lookup("item"))
rootCmd.Execute()
}
Output:
$ ./test.exe -i 'k=v,"a=b,c"'
item exists? true
GetString : [k=v,"a=b,c"]
Keys : [item]
Settings : map[item:[k=v,"a=b,c"]]
The usage of GetStringMap() and GetStringMapString() makes more sense with other formats like JSON, YAML, etc. You can trace the execution of these functions to JSON unmarshalling calls in debugging mode.
For map conversion, you can write your own function like this (live):
func getStringMap(s string) map[string]string {
entries := strings.Split(s, ",")
m := make(map[string]string)
for _, e := range entries {
tokens := strings.Split(e, "=")
k := strings.TrimSpace(tokens[0])
v := strings.TrimSpace(tokens[1])
m[k] = v
}
return m
}

Is there a way to determine whether a flag was set when using `flag.VisitAll`?

I'm using go's native "flag" package.
Built into it is the ability to visit all currently defined flags, using flag.VisitAll.
I'm trying to build a snippet that tries to fetch the value for that flag from an environment variable if one exists and in-case the flag was not set, and I can't find a way to determine whether a specific flag was set or not.
Is there any way to achieve that without implementing new parameter types?
Using flag.VisitAll sounds a bit convoluted; I'd suggest getting the environment variable with a sane default and using it as the flag's default value - meaning the environment variable will be the fallback if the flag isn't set:
package main
import (
"flag"
"fmt"
"os"
)
func GetEnvDefault(key, def string) string {
v := os.Getenv(key)
if v == "" {
return def
}
return v
}
func main() {
// Uncomment to test behaviour
// os.Setenv("SERVER_NAME", "donaldduck")
var serverName string
flag.StringVar(&serverName, "n", GetEnvDefault("SERVER_NAME", "mickeymouse"), "The human name for the server")
flag.Parse()
fmt.Println(serverName)
}
See: https://play.golang.org/p/ixDsXH31cBF
There is no function to walk over the unset command line flags. This functionality can be implemented, however, by taking the difference between the flags returned by VisitAll and Visit; the former walks over all flags, while the latter walks over set flags:
func UnsetFlags(fs *flag.FlagSet) []*flag.Flag {
var unset []*flag.Flag
fs.VisitAll(func(f *flag.Flag) {
unset = append(unset, f)
})
fs.Visit(func(f *flag.Flag) {
for i, h := range unset {
if f == h {
unset = append(unset[:i], unset[i+1:]...)
}
}
})
return unset
}
You can use that function after your flag.Parse call to set any unset flags to their environment value:
for _, f := range UnsetFlags(flag.CommandLine) {
v := os.Getenv(f.Name)
f.Value.Set(v)
}

How to Unmarshall Viper config value to struct containing array of string properly?

I noticed that this is perhaps a bug when viper tries to unmarshall to struct. To explain it better, consider this:
I have a cli command like below
dd-cli submit-bug --name "Bug 1" --tag reason1 --tag reason2
Here is my command line source code
package cmd
import (
"fmt"
"github.com/spf13/viper"
"github.com/spf13/cobra"
)
// SubmitBugOpts is a set of flags being exposed by this Deploy command
type SubmitBugOpts struct {
Name string `mapstructure:"bug-name"`
ReasonTags []string `mapstructure:"tags"`
}
var (
submitBugOpts = SubmitBugOpts{}
)
func submitBugRun(cmd *cobra.Command, args []string) {
fmt.Printf("Bug Name is %+v\n", submitBugOpts.Name)
fmt.Printf("List of tags is %+v\n", submitBugOpts.ReasonTags)
fmt.Printf("Length of tags is %d\n", len(submitBugOpts.ReasonTags))
for index, el := range submitBugOpts.ReasonTags {
fmt.Printf("tag[%d] = %s\n", index, el)
}
}
var submitBugCmd = &cobra.Command{
Use: "submit-bug",
Short: "Deploy/Install a helm chart to Kubernetes cluster",
Run: submitBugRun,
PreRun: func(cmd *cobra.Command, args []string) {
pFlags := cmd.PersistentFlags()
viper.BindPFlag("bug-name", pFlags.Lookup("name"))
viper.BindPFlag("tags", pFlags.Lookup("tag"))
fmt.Printf("Viper all setting value: %+v\n", viper.AllSettings())
fmt.Printf("Before unmarshall: %+v\n", submitBugOpts)
viper.Unmarshal(&submitBugOpts)
fmt.Printf("After unmarshall: %+v\n", submitBugOpts)
},
}
func init() {
rootCmd.AddCommand(submitBugCmd)
pFlags := submitBugCmd.PersistentFlags()
pFlags.StringVar(&submitBugOpts.Name, "name", "", "the bug name")
pFlags.StringArrayVar(&submitBugOpts.ReasonTags, "tag", nil, "the bug's reason tag. You can define it multiple times")
submitBugCmd.MarkPersistentFlagRequired("name")
submitBugCmd.MarkPersistentFlagRequired("tag")
}
I run this command:
dd-cli submit-bug --name "Bug 1" --tag reason1 --tag reason2
And the output is below
Viper all setting value: map[bug-name:Bug 1 tags:[reason1,reason2]]
Before unmarshall: {Name:Bug 1 ReasonTags:[reason1 reason2]}
After unmarshall: {Name:Bug 1 ReasonTags:[[reason1 reason2]]}
Bug Name is Bug 1
List of tags is [[reason1 reason2]]
Length of tags is 2
tag[0] = [reason1
tag[1] = reason2]
I expect the viper.Unmarshall() will correctly omit the [ for submitBugOpts.ReasonTags [0] and omit the ] for submitBugOpts.ReasonTags[1]. So the expected value of submitBugOpts.ReasonTags doesn't contains any [ and ] .
Any pointer how to fix this? I've submitted this issue on viper repo: https://github.com/spf13/viper/issues/527. However I am asking on SO just in case you guys know how to handle this too.
After digging into codes of github.com/spf13/{cobra,viper,pflag}for quite a while, I finally find the problem.
When you call pFlags.StringArrayVar(&submitBugOpts.ReasonTags, "tag", nil, ...), the ReasonTags, of course, binds to a wrapper of pflag.stringArrayValue. source.
And when you call viper.Unmarshall, viper uses v.Get to get the value binds to ReasonTags and then it calls v.find.
In v.find, after the value is found, it uses the wrapper's ValueType() method to determine its type, the wrapper then calls the Type method of the wrapped type, pflag.stringArrayValue, and returns "stringArray". source
But viper only handle "stringSlice" as a special case, so the value gets to default part of type switch, which uses its ValueString() method - making it into a string, with "[" and "]" on both side. source
And when finally unmarshalling, as your output param, ReasonTags is of []string, the program simply splitted the string and set it into the field.
As for solution, if you are ok with prohibitting tag to contain ,, simply change StringArrayVar to StringSliceVar, but this will results in --tag "Yet, the problem re-occurs" into []string{"Yet"," the problem re-occrus"}.
If that is critical, you'll need to ask the developers of viper to create a case for stringArray.

How to assign default value if env var is empty?

How do you assign a default value if an environment variable isn't set in Go?
In Python I could do mongo_password = os.getenv('MONGO_PASS', 'pass') where pass is the default value if MONGO_PASS env var isn't set.
I tried an if statement based on os.Getenv being empty, but that doesn't seem to work due to the scope of variable assignment within an if statement. And I'm checking for multiple env var's, so I can't act on this information within the if statement.
There's no built-in to fall back to a default value,
so you have to do a good old-fashioned if-else.
But you can always create a helper function to make that easier:
func getenv(key, fallback string) string {
value := os.Getenv(key)
if len(value) == 0 {
return fallback
}
return value
}
Note that as #michael-hausenblas pointed out in a comment,
keep in mind that if the value of the environment variable is really empty, you will get the fallback value instead.
Even better as #ŁukaszWojciechowski pointed out, using os.LookupEnv:
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
What you're looking for is os.LookupEnv combined with an if statement.
Here is janos's answer updated to use LookupEnv:
func getEnv(key, fallback string) string {
value, exists := os.LookupEnv(key)
if !exists {
value = fallback
}
return value
}
Go doesn't have the exact same functionality as Python here; the most idiomatic way to do it though, I can think of, is:
mongo_password := "pass"
if mp := os.Getenv("MONGO_PASS"); mp != "" {
mongo_password = mp
}
To have a clean code I do this:
myVar := getEnv("MONGO_PASS", "default-pass")
I defined a function that is used in the whole app
// getEnv get key environment variable if exist otherwise return defalutValue
func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if len(value) == 0 {
return defaultValue
}
return value
}
Had the same question as the OP and found someone encapsulated the answers from this thread into a nifty library that is fairly simple to use, hope this help others!
https://github.com/caarlos0/env
For more complex application you can use tooling such as viper, which allows you to set global custom default values, parse configuration files, set a prefix for your app's env var keys (to ensure consistency and name spacing of env var configurations) and many other cool features.
Sample code:
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.AutomaticEnv() // read value ENV variable
// Set default value
viper.SetEnvPrefix("app")
viper.SetDefault("linetoken", "DefaultLineTokenValue")
// Declare var
linetoken := viper.GetString("linetoken")
fmt.Println("---------- Example ----------")
fmt.Println("linetoken :", linetoken)
}
I also had the same problem and I just created a small package called getenvs exactly to answer this problem.
Getenvs supports string, bool, int and float and it can be used like below:
package main
import (
"fmt"
"gitlab.com/avarf/getenvs"
)
func main() {
value := getenvs.GetEnvString("STRING_GETENV", "default-string-value")
bvalue, _ := getenvs.GetEnvBool("BOOL_GETENV", false)
ivalue, _ := getenvs.GetEnvInt("INT_GETENV", 10)
fmt.Println(value)
fmt.Println(bvalue)
fmt.Println(ivalue)
}
In case you are OK with adding little dependency you can use something like https://github.com/urfave/cli
package main
import (
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "lang, l",
Value: "english",
Usage: "language for the greeting",
EnvVar: "APP_LANG",
},
}
app.Run(os.Args)
}

Resources