📜 ⬆️ ⬇️

A practical guide on environment variables in Go

Hi, Habr! I present to you the translation of the article A Go to Enda Phelan.

Environment variables are the best way to store application configurations, since they can be set at the system level. This is one of the principles of the Twelve-Factor App methodology, it allows you to separate applications from the system in which they are running (the configuration can vary significantly between deployments, the code should not differ).

Using environment variables


All that is needed to interact with environment variables is in the standard os library. This is how you can get the value of the PATH environment variable:

package main import ( "fmt" "os" ) func main() { // Store the PATH environment variable in a variable path, exists := os.LookupEnv("PATH") if exists { // Print the value of the environment variable fmt.Print(path) } } 

And so - set the value of the variable:
')
 package main import ( "fmt" "os" ) func main() { // Set the USERNAME environment variable to "MattDaemon" os.Setenv("USERNAME", "MattDaemon") // Get the USERNAME environment variable username := os.Getenv("USERNAME") // Prints out username environment variable fmt.Print(username) } 

Loading environment variables from a .env file


On a developer machine, where many projects are immediately launched, it is not always convenient to store parameters in variable environments; It would be more logical to divide them between projects using env-files. You can do this, for example, with the help of godotenv - this is the Ruby library dotenv ported to Go. It allows you to set the necessary for the application environment variables from the .env file.

To install the package, run:

 go get github.com/joho/godotenv 

Add settings to the .env file in the project root:

 GITHUB_USERNAME=craicoverflow GITHUB_API_KEY=TCtQrZizM1xeo1v92lsVfLOHDsF7TfT5lMvwSno 

Now you can use these values ​​in the application:

 package main import ( "log" "github.com/joho/godotenv" "fmt" "os" ) // init is invoked before main() func init() { // loads values from .env into the system if err := godotenv.Load(); err != nil { log.Print("No .env file found") } } func main() { // Get the GITHUB_USERNAME environment variable githubUsername, exists := os.LookupEnv("GITHUB_USERNAME") if exists { fmt.Println(githubUsername) } // Get the GITHUB_API_KEY environment variable githubAPIKey, exists := os.LookupEnv("GITHUB_API_KEY") if exists { fmt.Println(githubAPIKey) } } 

It is important to remember that if the value of the environment variable is set at the system level, Go will use this value instead of the one specified in the env file.

Wrapping environment variables in a configuration module


It is nice, of course, to have access to environment variables directly, as was shown above, but to support such a solution seems rather problematic. The name of a variable is a string, and if it changes, then imagine a headache in which the process of updating references to a variable throughout an application will result.

To solve this problem, create a configuration module for working with environment variables in a more centralized and supported way.

Here is a simple config module that returns the configuration parameters in the Config structure (also set the default values ​​for the parameters in case the environment variable does not appear in the system):

 package config import ( "os" ) type GitHubConfig struct { Username string APIKey string } type Config struct { GitHub GitHubConfig } // New returns a new Config struct func New() *Config { return &Config{ GitHub: GitHubConfig{ Username: getEnv("GITHUB_USERNAME", ""), APIKey: getEnv("GITHUB_API_KEY", ""), }, } } // Simple helper function to read an environment or return a default value func getEnv(key string, defaultVal string) string { if value, exists := os.LookupEnv(key); exists { return value } return defaultVal } 

Next, we add types to the Config structure, since the existing solution only supports string types, which is not very reasonable for large applications.

Create handlers for bool, slice and integer types:

 package config import ( "os" "strconv" "strings" ) type GitHubConfig struct { Username string APIKey string } type Config struct { GitHub GitHubConfig DebugMode bool UserRoles []string MaxUsers int } // New returns a new Config struct func New() *Config { return &Config{ GitHub: GitHubConfig{ Username: getEnv("GITHUB_USERNAME", ""), APIKey: getEnv("GITHUB_API_KEY", ""), }, DebugMode: getEnvAsBool("DEBUG_MODE", true), UserRoles: getEnvAsSlice("USER_ROLES", []string{"admin"}, ","), MaxUsers: getEnvAsInt("MAX_USERS", 1), } } // Simple helper function to read an environment or return a default value func getEnv(key string, defaultVal string) string { if value, exists := os.LookupEnv(key); exists { return value } return defaultVal } // Simple helper function to read an environment variable into integer or return a default value func getEnvAsInt(name string, defaultVal int) int { valueStr := getEnv(name, "") if value, err := strconv.Atoi(valueStr); err == nil { return value } return defaultVal } // Helper to read an environment variable into a bool or return default value func getEnvAsBool(name string, defaultVal bool) bool { valStr := getEnv(name, "") if val, err := strconv.ParseBool(valStr); err == nil { return val } return defaultVal } // Helper to read an environment variable into a string slice or return default value func getEnvAsSlice(name string, defaultVal []string, sep string) []string { valStr := getEnv(name, "") if valStr == "" { return defaultVal } val := strings.Split(valStr, sep) return val } 

Add new environment variables to our env-file:

 GITHUB_USERNAME=craicoverflow GITHUB_API_KEY=TCtQrZizM1xeo1v92lsVfLOHDsF7TfT5lMvwSno MAX_USERS=10 USER_ROLES=admin,super_admin,guest DEBUG_MODE=false 

Now you can use them anywhere in the application:

 package main import ( "fmt" "log" "github.com/craicoverflow/go-environment-variables-example/config" "github.com/joho/godotenv" ) // init is invoked before main() func init() { // loads values from .env into the system if err := godotenv.Load(); err != nil { log.Print("No .env file found") } } func main() { conf := config.New() // Print out environment variables fmt.Println(conf.GitHub.Username) fmt.Println(conf.GitHub.APIKey) fmt.Println(conf.DebugMode) fmt.Println(conf.MaxUsers) // Print out each role for _, role := range conf.UserRoles { fmt.Println(role) } } 

Done!


Yes, there are packages that offer a ready-made solution for configuring your application, but how necessary are they, if it is so easy to do it yourself?

How do you manage the configuration in your application?

Source: https://habr.com/ru/post/446468/


All Articles