환경 설정 파일들을 다루기 위해 Viper Package 를 사용한다.
viper 은 유명한 configuration 패키지로써 많은 Go 프로젝트들 내에서 사용되었다.
환경 변수는 실행 환경 정보의 값을 지니고 있다.
Go 에서 env variable 을 관리하기 위해 자주 사용되는 패키지들은 세가지가 있다.
- os package
- godotenv package
- viper package
이 중 viper package 에 대해 알아보자.
go get github.com/spf13/viper
Instance
viper 인스턴스 생성하기
viper.New()
Set File & Path
config 파일과 경로를 설정하기 위해서 SetConfigFile
이라는 메서드를 사용한다.
viper.SetConfigFile(".env")
- Viper 는 env 파일 외에 다양한 파일들을 지원한다.
- HCL
- TOML
- JSON
- YAML
- etc..
Read Config File
viper 에서 config 파일을 읽어들이는 메서드는 두 가지가 있다.
-
ReadConfig()
- yaml
- json
-
ReadInConfig()
- byte buffer
Configuration 파일을 읽어들인다.
func (v *Viper) ReadConfig(in io.Reader) error {
v.config = make(map[string]interface{})
return v.unmarshalReader(in, v.config)
}
- key 가 파일내에 존재하지 않을 때 해당 key 는 nil 값으로 설정된다.
미리 설정해둔 경로로 가서 디스크나 키-값 스토리지에 있는 config 파일을 찾아 읽어들인다.
- 설정된 경로의 예)
viper.AddConfigPath(currentdir())
func (v *Viper) ReadInConfig() error {
jww.INFO.Println("Attempting to read in config file")
filename, err := v.getConfigFile()
if err != nil {
return err
}
if !stringInSlice(v.getConfigType(), SupportedExts) {
return UnsupportedConfigError(v.getConfigType())
}
jww.DEBUG.Println("Reading file: ", filename)
file, err := afero.ReadFile(v.fs, filename)
if err != nil {
return err
}
config := make(map[string]interface{})
err = v.unmarshalReader(bytes.NewReader(file), config)
if err != nil {
return err
}
v.config = config
return nil
}
현재의 viper configuration 을 파일에 쓴다. 파일이 작성되는 위치는 미리 지정해둔 경로이다.
만약 경로가 설정되 있지 않다면 err 를 리턴한다.
func (v *Viper) WriteConfig() error {
filename, err := v.getConfigFile()
if err != nil {
return err
}
return v.writeConfig(filename, true)
}
SafeWriteConfig 는 해당 경로에 config 파일이 존재하지 않을시에만 파일을 작성한다.
그로인해 덮어쓰기가 발생하지 않는다. 안전한 파일 작성이 가능하다.
func (v *Viper) SafeWriteConfig() error {
if len(v.configPaths) < 1 {
return errors.New("missing configuration for 'configPath'")
}
return v.SafeWriteConfigAs(filepath.Join(v.configPaths[0], v.configName+"."+v.configType))
}
Set을 통해 직접 config 의 key 와 value 를 설정
func (v *Viper) Set(key string, value interface{}) {
key = v.realKey(strings.ToLower(key))
value = toCaseInsensitiveValue(value)
path := strings.Split(key, v.keyDelim)
lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(v.override, path[0:len(path)-1])
deepestMap[lastKey] = value
}
- Key 는 case-insensitive
새 config 파일과 기존에 존재하는 config 파일을 합병시킨다.
func (v *Viper) MergeInConfig() error {
jww.INFO.Println("Attempting to merge in config file")
filename, err := v.getConfigFile()
if err != nil {
return err
}
if !stringInSlice(v.getConfigType(), SupportedExts) {
return UnsupportedConfigError(v.getConfigType())
}
file, err := afero.ReadFile(v.fs, filename)
if err != nil {
return err
}
return v.MergeConfig(bytes.NewReader(file))
}