如何避免將密碼提交到代碼庫(kù)了?詳解 Go 中的環(huán)境變量
了解環(huán)境變量以及在 Golang 應(yīng)用程序中使用它們的不同方法。
開(kāi)始之前
本教程假定你具有:
對(duì) Go 語(yǔ)言的基本了解 系統(tǒng)上安裝了最新 Golang 版本 幾分鐘的時(shí)間
在本文中,我們將了解環(huán)境變量以及為什么要使用它們。并且將使用內(nèi)置和第三方包在 Go 應(yīng)用程序中訪問(wèn)它們。
什么是環(huán)境變量?
環(huán)境變量是系統(tǒng)級(jí)的鍵-值對(duì),正在運(yùn)行的進(jìn)程可以訪問(wèn)它。這些通常用于使同一程序在不同的部署環(huán)境(例如 PROD, DEV 或 TEST)中表現(xiàn)不同。在環(huán)境中存儲(chǔ)配置是 twelve-factor 應(yīng)用程序的原理之一。它使應(yīng)用程序具有可移植性。
為什么要使用環(huán)境變量
如果您在代碼中使用敏感信息,那么所有有權(quán)訪問(wèn)該代碼的未授權(quán)用戶都將擁有敏感數(shù)據(jù),您可能不希望如此。 如果您使用的代碼版本控制工具如: git,那么可能將 DB 憑據(jù)與代碼一起推送,它將公開(kāi)。如果要在一處管理變量,則可以進(jìn)行任何更改,而不必在應(yīng)用程序代碼中的所有位置都進(jìn)行更改。 您可以管理多個(gè)部署環(huán)境,例如 PROD,DEV 或 TEST。在部署之間可以輕松更改環(huán)境變量,而無(wú)需更改任何應(yīng)用程序代碼。
永遠(yuǎn)不要忘記在 .gitignore 中包含環(huán)境變量文件
內(nèi)置操作系統(tǒng)包
您不需要任何外部程序包即可訪問(wèn) Golang 中的環(huán)境變量,并且可以使用標(biāo)準(zhǔn)庫(kù) os 包來(lái)實(shí)現(xiàn)。以下是與環(huán)境變量有關(guān)的函數(shù)及其用途的列表。
os.Setenv()設(shè)置環(huán)境值的值。os.Getenv()獲取指定鍵對(duì)應(yīng)的環(huán)境變量值。os.Unsetenv()刪除指定鍵命名對(duì)應(yīng)的單個(gè)環(huán)境值,如果我們?cè)賴L試使用os.Getenv()來(lái)獲取該環(huán)境值,將返回一個(gè)空值。os.ExpandEnv根據(jù)環(huán)境變量的值替換字符串中的${var}或$var。如果不存在任何環(huán)境變量,則將使用空字符串替換它。os.LookupEnv()獲取指定鍵對(duì)應(yīng)的環(huán)境變量值。如果系統(tǒng)中不存在該變量,則返回值將為空,并且布爾值將為 false。否則,它將返回值(可以為空),并且布爾值為 true。
如果不存在環(huán)境變量,則 os.Getenv() 將返回一個(gè)空字符串,使用 LookupEnv 來(lái)區(qū)分空值和未設(shè)置值。
現(xiàn)在,讓我們?cè)诖a中使用上述所有函數(shù)。在一個(gè)空文件夾中創(chuàng)建一個(gè) main.go 文件。
package?main
import?(
??"fmt"
??"os"
)
func?main()?{
??//?Set?Environment?Variables
??os.Setenv("SITE_TITLE",?"Test?Site")
??os.Setenv("DB_HOST",?"localhost")
??os.Setenv("DB_PORT",?"27017")
??os.Setenv("DB_USERNAME",?"admin")
??os.Setenv("DB_PASSWORD",?"password")
??os.Setenv("DB_NAME",?"testdb")
??//?Get?the?value?of?an?Environment?Variable
??host?:=?os.Getenv("SITE_TITLE")
??port?:=?os.Getenv("DB_HOST")
??fmt.Printf("Site?Title:?%s,?Host:?%s\n",?host,?port)
??//?Unset?an?Environment?Variable
??os.Unsetenv("SITE_TITLE")
??fmt.Printf("After?unset,?Site?Title:?%s\n",?os.Getenv("SITE_TITLE"))
??//Checking?that?an?environment?variable?is?present?or?not.
??redisHost,?ok?:=?os.LookupEnv("REDIS_HOST")
??if?!ok?{
????fmt.Println("REDIS_HOST?is?not?present")
??}?else?{
????fmt.Printf("Redis?Host:?%s\n",?redisHost)
??}
??//?Expand?a?string?containing?environment?variables?in?the?form?of?$var?or?${var}
??dbURL?:=?os.ExpandEnv("mongodb://${DB_USERNAME}:${DB_PASSWORD}@$DB_HOST:$DB_PORT/$DB_NAME")
??fmt.Println("DB?URL:?",?dbURL)
}
下面是我們?cè)诮K端中執(zhí)行 ?go run main.go ?的輸出:
go?run?main.go
//?output
Site?Title:?Test?Site,?Host:?localhost
After?unset,?Site?Title:?27017
REDIS_HOST?is?not?present
DB?URL:??mongodb://admin:password@localhost:27017/testdb
還有兩個(gè)函數(shù) os.Clearenv 和 os.Environ(),讓我們?cè)趩为?dú)的程序中使用它們。
os.Clearenv?刪除所有環(huán)境變量,清理測(cè)試環(huán)境可能很有用os.Environ()以 key = value 的形式返回包含所有環(huán)境變量的字符串的一部分。
package?main
import?(
??"fmt"
??"os"
??"strings"
)
func?main()?{
??//?Environ?returns?a?slice?of?string?containing?all?the?environment?variables?in?the?form?of?key=value.
??for?_,?env?:=?range?os.Environ()?{
????//?env?is
????envPair?:=?strings.SplitN(env,?"=",?2)
????key?:=?envPair[0]
????value?:=?envPair[1]
????fmt.Printf("%s?:?%s\n",?key,?value)
??}
??//?Delete?all?environment?variables
??os.Clearenv()
??fmt.Println("Number?of?environment?variables:?",?len(os.Environ()))
}
上面的函數(shù)將列出系統(tǒng)中所有可用的環(huán)境變量,包括 NAME 和 DB_HOST。一旦運(yùn)行os.Clearenv(),它將清除正在運(yùn)行的進(jìn)程的所有環(huán)境變量。
GoDotEnv 包
Ruby dotenv 項(xiàng)目啟發(fā)了 GoDotEnv[1] 包,它從 .env 文件加載環(huán)境變量。
讓我們創(chuàng)建一個(gè) .env 文件,其中包含所有配置。
# .env file
# This is a sample config file
SITE_TITLE=Test Site
DB_HOST=localhost
DB_PORT=27017
DB_USERNAME=admin
DB_PASSWORD=password
DB_NAME=testdb
然后在 main.go 文件中,我們將使用 godotenv 加載環(huán)境變量。
我們也可以一次加載多個(gè) env 文件。godotenv 還支持 YAML。
//?main.go
package?main
import?(
??"fmt"
??"log"
??"os"
??"github.com/joho/godotenv"
)
func?main()?{
??//?load?.env?file?from?given?path
??//?we?keep?it?empty?it?will?load?.env?from?current?directory
??err?:=?godotenv.Load(".env")
??if?err?!=?nil?{
????log.Fatalf("Error?loading?.env?file")
??}
??//?getting?env?variables?SITE_TITLE?and?DB_HOST
??siteTitle?:=?os.Getenv("SITE_TITLE")
??dbHost?:=?os.Getenv("DB_HOST")
??fmt.Printf("godotenv?:?%s?=?%s?\n",?"Site?Title",?siteTitle)
??fmt.Printf("godotenv?:?%s?=?%s?\n",?"DB?Host",?dbHost)
}
打開(kāi)終端并運(yùn)行 ?main.go:
go?run?main.go
//?output
godotenv?:?Site?Title?=?Test?Site
godotenv?:?DB?Host?=?localhost
Viper 包
Viper 是 Go 應(yīng)用程序的配置的完整解決方案。它旨在在應(yīng)用程序中工作,并且可以處理所有類型的配置需求和格式。
Viper[2] 支持多種文件格式來(lái)加載環(huán)境變量,例如,從 JSON,TOML,YAML,HCL,envfile 和 Java 屬性配置文件(properties)中讀取。因此,在此示例中,我們將研究如何從 YAML 文件中加載環(huán)境變量。
YAML 是一種人類可讀的數(shù)據(jù)序列化語(yǔ)言。它通常用于配置文件和用于存儲(chǔ)或傳輸數(shù)據(jù)的應(yīng)用程序。
讓我們?cè)谝粋€(gè)空文件夾中創(chuàng)建 config.yaml 和 main.go。
#?config.yaml
SITE:
??TITLE:?Test?Site
DB:
??HOST:?"localhost"
??PORT:?"27017"
??USERNAME:?"admin"
??PASWORD:?"password"
??NAME:?"testdb"
在下面的代碼中,我們使用 Viper 從 config.yaml 中加載環(huán)境變量。我們可以從所需的任何路徑加載配置文件。如果配置文件中沒(méi)有任何環(huán)境變量,我們還可以為任何環(huán)境變量設(shè)置默認(rèn)值。
//?main.go
package?main
import?(
??"fmt"
??"log"
??"os"
??"github.com/spf13/viper"
)
func?main()?{
??//?Set?the?file?name?of?the?configurations?file
??viper.SetConfigName("config")
??//?Set?the?path?to?look?for?the?configurations?file
??viper.AddConfigPath(".")
??//?Enable?VIPER?to?read?Environment?Variables
??viper.AutomaticEnv()
??viper.SetConfigType("yml")
??if?err?:=?viper.ReadInConfig();?err?!=?nil?{
????fmt.Printf("Error?reading?config?file,?%s",?err)
??}
??//?Set?undefined?variables
??viper.SetDefault("DB.HOST",?"127.0.0.1")
??//?getting?env?variables?DB.PORT
??//?viper.Get()?returns?an?empty?interface{}
??//?so?we?have?to?do?the?type?assertion,?to?get?the?value
??DBPort,?ok?:=?viper.Get("DB.PORT").(string)
??//?if?type?assert?is?not?valid?it?will?throw?an?error
??if?!ok?{
????log.Fatalf("Invalid?type?assertion")
??}
??fmt.Printf("viper?:?%s?=?%s?\n",?"Database?Port",?DBPort)
}
打開(kāi)終端并運(yùn)行 main.go:
go?run?main.go
//?output
viper?:?Database?Port?=?27017
結(jié)論
使用環(huán)境變量是在我們的應(yīng)用程序中處理配置的絕佳方法。總體而言,它為您提供了輕松的配置,更好的安全性,多個(gè)部署環(huán)境以及更少的生產(chǎn)錯(cuò)誤。
現(xiàn)在您可以在 go 應(yīng)用程序中管理環(huán)境變量,并且可以在我們的 Github Repo[3] 上找到本教程中使用的完整代碼。
原文鏈接:https://www.loginradius.com/engineering/blog/environment-variables-in-golang/
作者:Puneet Singh
編譯:polarisxu
參考資料
GoDotEnv: https://github.com/joho/godotenv
[2]Viper: https://github.com/spf13/viper
[3]Github Repo: https://github.com/LoginRadius/engineering-blog-samples/tree/master/GoLang/EnvironmentVariables
推薦閱讀
