Парсинг конфигов в Go с помощью Viper

11 декабря 2019

Редкая программа обходится без файла конфигурации. Даже если вы пишите простенький REST-сервис, то ему как минимум нужно знать, какой порт и на каком интерфейсе слушать, а также где искать PostgreSQL. Что уж говорить о более сложных приложениях. Для чтения конфигов в проектах на Go часто используют библиотеку spf13/viper.

Почему Viper? Потому что он умеет очень много всего:

  • Поддерживаются форматы Yaml, Json, INI и другие;
  • Параметры конфигурации можно переопределять через флаги;
  • Также параметры можно задавать через переменные окружения, как это принято в Kubernetes;
  • Viper умеет забирать параметры из etcd и Consul;
  • Параметры можно менять на лету, что позволяет изменить поведение программы без перезапуска;

Другими словами, какие бы требования не предъявлялись к приложению, Viper почти наверняка сможет их удовлетворить. Вот все им и пользуются.

Все возможности библиотеки мы рассматривать не будем, так как их много и получится пересказ документации. Рассмотрим только самый базовый пример использования, который обычно и нужен:

func run(configPath string) {
    customFormatter := new(log.TextFormatter)
    customFormatter.TimestampFormat = "2006-01-02 15:04:05"
    customFormatter.FullTimestamp = true
    log.SetFormatter(customFormatter)

    viper.AutomaticEnv()
    viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
    viper.SetEnvPrefix("restexample")

    viper.SetDefault("loglevel", "debug")
    viper.SetDefault("listen", "localhost:8080")
    viper.SetDefault("db.url", "postgres://localhost")

    if configPath != "" {
        log.Infof("Parsing config: %s", configPath)
        viper.SetConfigFile(configPath)
        err := viper.ReadInConfig()
        if err != nil {
            log.Panicf("Unable to read config file: %s", err)
        }
    } else {
        log.Infof("Config file is not specified.")
    }

    logLevelString := viper.GetString("loglevel")
    logLevel, err := log.ParseLevel(logLevelString)
    if err != nil {
        log.Panicf("Unable to parse loglevel: %s", logLevelString)
    }

    log.SetLevel(logLevel)
    log.Infof("Listen: %s", viper.GetString("listen"))
    log.Infof("DB URL: %s", viper.GetString("db.url"))
}

Путь до файла конфигурации передается приложению при помощи обязательного флага --config или его синонима -c, как это ранее было описано в посте Парсинг флагов и аргументов в языке Go.

Пример файла конфигурации:

loglevel: debug
listen
: 0.0.0.0:8080
db
:
  url
: postgres://postgresql@localhost/restservice

Варианты запуска приложения:

$ ./rest
Config file is not specified.
Listen: localhost:8080
DB URL: postgres://localhost

$ ./rest -c ./config.yaml
Parsing config: ./config.yaml
Listen: 0.0.0.0:8080
DB URL: postgres://restservice@localhost/restservice

$ RESTEXAMPLE_LISTEN=localhost:80 ./rest -c ./config.yaml
Parsing config: ./config.yaml
Listen: localhost:80
DB URL: postgres://restservice@localhost/restservice

$ RESTEXAMPLE_LISTEN=localhost:80 ./rest
Config file is not specified.
Listen: localhost:80
DB URL: postgres://localhost

В выводе опущены таймстемпы, так как они нам сейчас не интересны.

Первый вариант демонстрирует запуск без указания файла конфигурации и каких-либо переменных окружения. В этом случае используются параметры по умолчанию, указанные в коде. Если указать файл конфигурации, как во втором варианте, то параметры из файла перезатрут параметры по умолчанию. Наконец, третий и четвертый варианты запуска демонстрируют, что переменные окружения имеют приоритет как перед параметрами по умолчанию, так и перед параметрами из файла конфигурации.

Как видите, ничего сложного! Наиболее полную и актуальную информацию, как обычно, вы найдете в официальной документации.

Метки: .

Понравился пост? Узнайте, как можно поддержать развитие этого блога.

Также подпишитесь на RSS, ВКонтакте, Twitter или Telegram.