Viper
Адрес репозитория: spf13/viper: Go configuration with fangs (github.com)
Адрес документации: spf13/viper: Go configuration with fangs (github.com)
TIP
В настоящее время обсуждается переход на Viper2. Заинтересованные могут ознакомиться: Viper2
Установка
go get github.com/spf13/viperВведение
Viper — это полное решение для файлов конфигурации в приложениях Go. Может обрабатывать почти все типы конфигурационных потребностей и форматов, удобно управляя файлами конфигурации проекта. Обладает следующими особенностями:
- Установка значений по умолчанию
- Поддержка форматов JSON, TOML, YAML, HCL, envfile, Java properties
- Поддержка мониторинга в реальном времени и перезагрузки файлов конфигурации
- Поддержка чтения из переменных окружения
- Поддержка чтения из удалённых систем конфигурации и мониторинга изменений
- Поддержка чтения из флагов командной строки
- Поддержка чтения из буфера
- Поддержка явных установленных значений
Официально заявлено, что Viper может удовлетворить все потребности в конфигурации приложений. Разработчикам нужно только сосредоточиться на создании приложений, а Viper возьмёт на себя управление конфигурацией. Многие известные проекты используют Viper:
DANGER
Viper не отвечает за шифрование и дешифрование файлов конфигурации, то есть не выполняет никакой обработки безопасности файлов конфигурации.
Порядок чтения
Viper использует следующий приоритет для чтения конфигурации:
- Явно установленные значения
- Флаги командной строки
- Переменные окружения
- Файлы конфигурации
- Хранилища ключ-значение
- Значения по умолчанию
TIP
Ключи в конфигурации Viper нечувствительны к регистру. В будущих обсуждениях это может стать опциональным.
Значения по умолчанию
Хорошая система конфигурации должна поддерживать установку значений по умолчанию. Хотя это не всегда необходимо, это очень полезно, когда файл конфигурации не установлен. Пример:
viper.SetDefault("filePath", "./dir/img/usr")
viper.SetDefault("root", "123456")Чтение файлов конфигурации
Viper требует минимальной конфигурации, чтобы знать, где искать файлы конфигурации. Viper поддерживает JSON, TOML, YAML, HCL, INI, envfile и JavaProperties файлы. Viper может искать несколько путей одновременно, но в настоящее время один экземпляр Viper поддерживает только один файл конфигурации. Viper не устанавливает пути поиска по умолчанию, оставляя решение приложению.
Ниже приведён пример чтения файла конфигурации с помощью Viper. Не нужно указывать полный путь, но при использовании следует предоставить хотя бы один файл конфигурации.
func TestReadConfigFile(t *testing.T) {
viper.SetConfigName("config.yml") // Чтение файла конфигурации с именем config, без указания расширения
viper.SetConfigType("yaml") // При отсутствии расширения файла необходимо указать тип файла
viper.AddConfigPath("./") // Поиск в текущей папке
viper.AddConfigPath("$HOME/") // Использование переменной
viper.AddConfigPath(".") // Поиск в рабочем каталоге
err := viper.ReadInConfig() // Чтение конфигурации
if err != nil {
log.Fatalln(err)
}
}Также можно отдельно обрабатывать случай, когда файл конфигурации не найден:
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Файл конфигурации не найден
} else {
// Другие типы ошибок
}
}Ниже приведены все функции для доступа к конфигурации:
Get(key string) : interface{}GetBool(key string) : boolGetFloat64(key string) : float64GetInt(key string) : intGetIntSlice(key string) : []intGetString(key string) : stringGetStringMap(key string) : map[string]interface{}GetStringMapString(key string) : map[string]stringGetStringSlice(key string) : []stringGetTime(key string) : time.TimeGetDuration(key string) : time.DurationIsSet(key string) : boolAllSettings() : map[string]interface{}
При доступе к вложенной конфигурации используется разделитель ., например:
{
"server":{
"database":{
"url": "mysql...."
}
}
}Можно выполнить вложенный доступ через GetString("server.database.url").
Запись в файл конфигурации
Viper предоставляет набор функций для удобной записи конфигурации, хранящейся во время выполнения, в файл конфигурации.
// WriteConfig записывает конфигурацию в исходный файл конфигурации, ошибка если не существует, перезаписывает если существует
func WriteConfig() error { return v.WriteConfig() }
// SafeWriteConfig безопасно записывает конфигурацию в исходный файл конфигурации, записывает если не существует, не перезаписывает если существует
func SafeWriteConfig() error { return v.SafeWriteConfig() }
// WriteConfigAs записывает текущую конфигурацию в указанный файл, ошибка если файл не существует, перезаписывает если существует
func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) }
// SafeWriteConfigAs не перезаписывает исходный файл конфигурации, если указанный файл существует, ошибка если файл существует
func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) }Ниже приведены некоторые примеры:
func TestWritingConfig(t *testing.T) {
viper.WriteConfig() // Запись конфигурации в исходный файл конфигурации, эти файлы конфигурации должны быть заранее определены через 'viper.AddConfigPath()' и 'viper.SetConfigName'
viper.SafeWriteConfig()
viper.WriteConfigAs("/path/to/my/.config")
viper.SafeWriteConfigAs("/path/to/my/.config") // Будет возвращена ошибка, так как указанный файл существует
viper.SafeWriteConfigAs("/path/to/my/.other_config")
}Мониторинг и перезагрузка конфигурации
Viper позволяет приложениям динамически читать файл конфигурации во время выполнения, то есть обновлённая конфигурация вступает в силу без перезапуска приложения, не пропуская ни одной детали изменения. Нужно просто указать экземпляру Viper следить за изменениями конфигурации или предоставить функцию viper для выполнения при каждом изменении.
func TestWatchingConfig(t *testing.T) {
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Файл конфигурации изменён:", e.Name)
})
viper.WatchConfig()
}Псевдонимы
func TestAliases(t *testing.T) {
viper.RegisterAlias("a", "b")
viper.Set("a", 1)
viper.Set("b", 2) // Переопределит конфигурацию a
fmt.Println(viper.GetInt("a"))
}Извлечение подструктуры
Ранее упоминалось, что для доступа к вложенной конфигурации используется разделитель .. Также можно извлечь подструктуру с помощью функции viper.Sub(), возвращающей экземпляр Viper. Пример:
cache:
cache1:
max-items: 100
item-size: 64
cache2:
max-items: 200
item-size: 80cache1Config := viper.Sub("cache.cache1")
if cache1Config == nil { // Возвращает nil, если не существует
panic("конфигурация cache1 не существует")
}Установка разделителя вложенности
Когда нужно указать ключ, содержащий ., необходимо вручную указать другой разделитель, чтобы избежать ошибочного парсинга. Например:
viper.KeyDelimiter("/") // Установка разделителя в /Десериализация
Viper предоставляет две функции для десериализации конфигурации в структуру или map, поддерживая вложенные структуры:
Unmarshal(rawVal interface{}) : errorUnmarshalKey(key string, rawVal interface{}) : error
type config struct {
Port int
Name string
PathMap string `mapstructure:"path_map"`
}
var C config
err := viper.Unmarshal(&C)
if err != nil {
t.Fatalf("не удалось десериализовать в структуру, %v", err)
}Сериализация
Сериализация текущей конфигурации в строку определённого формата для хранения в файле конфигурации. Обычно поддерживаются JSON, TOML, YAML, HCL, envfile, Java properties.
TIP
Viper также поддерживает пользовательские форматы сериализации. Decoding custom formats with Viper - Márk Sági-Kazár (sagikazarmark.hu)
import (
yaml "gopkg.in/yaml.v2"
// ...
)
func yamlStringSettings() string {
c := viper.AllSettings()
bs, err := yaml.Marshal(c)
if err != nil {
log.Fatalf("не удалось сериализовать конфигурацию в YAML: %v", err)
}
return string(bs)
}Несколько экземпляров
Обычно достаточно глобального экземпляра, предоставляемого Viper. Однако, поскольку один экземпляр может сопоставлять только один файл конфигурации, можно создать несколько экземпляров для выполнения дополнительных операций. Например:
x := viper.New()
y := viper.New()
x.SetDefault("ContentDir", "content")
y.SetDefault("ContentDir", "foobar")
//...Интеграция с флагами командной строки
Viper поддерживает интеграцию с флагами командной строки через пакет pflag:
import (
"flag"
"github.com/spf13/viper"
)
func main() {
// Определение флагов
flag.String("config", "config.yaml", "путь к файлу конфигурации")
flag.Int("port", 8080, "порт сервера")
flag.Parse()
// Привязка флагов к viper
viper.BindPFlag("config", flag.Lookup("config"))
viper.BindPFlag("port", flag.Lookup("port"))
// Чтение конфигурации
viper.SetConfigFile(viper.GetString("config"))
viper.ReadInConfig()
// Получение значений
port := viper.GetInt("port")
fmt.Println("Порт:", port)
}Интеграция с переменными окружения
Viper автоматически читает переменные окружения:
// Автоматическое чтение всех переменных окружения
viper.AutomaticEnv()
// Установка префикса для переменных окружения
viper.SetEnvPrefix("MYAPP")
// Привязка конкретной переменной окружения
viper.BindEnv("database.url", "DATABASE_URL")Удалённая конфигурация
Viper поддерживает чтение конфигурации из удалённых систем, таких как etcd, Consul:
import (
"github.com/spf13/viper"
_ "github.com/spf13/viper/remote"
)
func main() {
viper.AddRemoteProvider("etcd", "http://localhost:4001", "/config/app")
viper.SetConfigType("yaml")
err := viper.ReadRemoteConfig()
if err != nil {
log.Fatal(err)
}
// Мониторинг изменений
viper.WatchRemoteConfig()
}Заключение
Viper — это мощная и гибкая библиотека для управления конфигурацией в Go-приложениях. Благодаря поддержке множества форматов, возможности мониторинга изменений, интеграции с переменными окружения и флагами командной строки, Viper стал стандартом де-факто для управления конфигурацией в экосистеме Go.
