230 lines
5.6 KiB
Go
230 lines
5.6 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/spf13/pflag"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const (
|
|
envPrefix = "FRIENDICA_"
|
|
envListenAddress = envPrefix + "LISTEN_ADDRESS"
|
|
envTimeout = envPrefix + "TIMEOUT"
|
|
envServerURL = envPrefix + "SERVER"
|
|
envStatsToken = envPrefix + "STATS_TOKEN"
|
|
envTLSSkipVerify = envPrefix + "TLS_SKIP_VERIFY"
|
|
)
|
|
|
|
// RunMode signals what the main application should do after parsing the options.
|
|
type RunMode int
|
|
|
|
const (
|
|
// RunModeExporter is the normal operation as an exporter serving metrics via HTTP.
|
|
RunModeExporter RunMode = iota
|
|
// RunModeHelp shows information about available options.
|
|
RunModeHelp
|
|
// RunModeVersion shows version information.
|
|
RunModeVersion
|
|
)
|
|
|
|
func (m RunMode) String() string {
|
|
switch m {
|
|
case RunModeExporter:
|
|
return "exporter"
|
|
case RunModeHelp:
|
|
return "help"
|
|
case RunModeVersion:
|
|
return "version"
|
|
default:
|
|
return "error"
|
|
}
|
|
}
|
|
|
|
// Config contains the configuration options for friendica-exporter.
|
|
type Config struct {
|
|
ListenAddr string `yaml:"listenAddress"`
|
|
Timeout time.Duration `yaml:"timeout"`
|
|
ServerURL string `yaml:"server"`
|
|
StatsToken string `yaml:"statsToken"`
|
|
TLSSkipVerify bool `yaml:"tlsSkipVerify"`
|
|
RunMode RunMode
|
|
}
|
|
|
|
var (
|
|
errValidateNoServerURL = errors.New("need to set a server URL")
|
|
errValidateNoStatsToken = errors.New("need to provide a stats token")
|
|
)
|
|
|
|
// Validate checks if the configuration contains all necessary parameters.
|
|
func (c Config) Validate() error {
|
|
if len(c.ServerURL) == 0 {
|
|
return errValidateNoServerURL
|
|
}
|
|
|
|
if len(c.StatsToken) == 0 {
|
|
return errValidateNoStatsToken
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get loads the configuration. Flags, environment variables and configuration file are considered.
|
|
func Get() (Config, error) {
|
|
return parseConfig(os.Args, os.Getenv)
|
|
}
|
|
|
|
func parseConfig(args []string, envFunc func(string) string) (Config, error) {
|
|
result, configFile, err := loadConfigFromFlags(args)
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("error parsing flags: %w", err)
|
|
}
|
|
|
|
if configFile != "" {
|
|
rawFile, err := loadConfigFromFile(configFile)
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("error reading configuration file: %w", err)
|
|
}
|
|
|
|
result = mergeConfig(result, rawFile)
|
|
}
|
|
|
|
env, err := loadConfigFromEnv(envFunc)
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("error reading environment variables: %w", err)
|
|
}
|
|
result = mergeConfig(result, env)
|
|
|
|
if strings.HasPrefix(result.StatsToken, "@") {
|
|
fileName := strings.TrimPrefix(result.StatsToken, "@")
|
|
statsToken, err := readStatsTokenFile(fileName)
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("can not read stats token file: %w", err)
|
|
}
|
|
|
|
result.StatsToken = statsToken
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func defaultConfig() Config {
|
|
return Config{
|
|
ListenAddr: ":9205",
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
}
|
|
|
|
func loadConfigFromFlags(args []string) (result Config, configFile string, err error) {
|
|
defaults := defaultConfig()
|
|
|
|
flags := pflag.NewFlagSet(args[0], pflag.ContinueOnError)
|
|
flags.StringVarP(&configFile, "config-file", "c", "", "Path to YAML configuration file.")
|
|
flags.StringVarP(&result.ListenAddr, "addr", "a", defaults.ListenAddr, "Address to listen on for connections.")
|
|
flags.DurationVarP(&result.Timeout, "timeout", "t", defaults.Timeout, "Timeout for getting server info document.")
|
|
flags.StringVarP(&result.ServerURL, "server", "s", "", "URL to Friendica server.")
|
|
flags.StringVarP(&result.StatsToken, "stats-token", "u", defaults.StatsToken, "Token for statistics Endpoint of Friendica.")
|
|
flags.BoolVar(&result.TLSSkipVerify, "tls-skip-verify", defaults.TLSSkipVerify, "Skip certificate verification of Friendica server.")
|
|
modeVersion := flags.BoolP("version", "V", false, "Show version information and exit.")
|
|
|
|
if err := flags.Parse(args[1:]); err != nil {
|
|
if err == pflag.ErrHelp {
|
|
return Config{
|
|
RunMode: RunModeHelp,
|
|
}, "", nil
|
|
}
|
|
|
|
return Config{}, "", err
|
|
}
|
|
|
|
if *modeVersion {
|
|
return Config{
|
|
RunMode: RunModeVersion,
|
|
}, "", nil
|
|
}
|
|
|
|
return result, configFile, nil
|
|
}
|
|
|
|
func loadConfigFromFile(fileName string) (Config, error) {
|
|
file, err := os.Open(fileName)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
var result Config
|
|
if err := yaml.NewDecoder(file).Decode(&result); err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func loadConfigFromEnv(getEnv func(string) string) (Config, error) {
|
|
tlsSkipVerify := false
|
|
if rawValue := getEnv(envTLSSkipVerify); rawValue != "" {
|
|
value, err := strconv.ParseBool(rawValue)
|
|
if err != nil {
|
|
return Config{}, fmt.Errorf("can not parse value for %q: %s", envTLSSkipVerify, rawValue)
|
|
}
|
|
tlsSkipVerify = value
|
|
}
|
|
|
|
result := Config{
|
|
ListenAddr: getEnv(envListenAddress),
|
|
ServerURL: getEnv(envServerURL),
|
|
StatsToken: getEnv(envStatsToken),
|
|
TLSSkipVerify: tlsSkipVerify,
|
|
}
|
|
|
|
if raw := getEnv(envTimeout); raw != "" {
|
|
value, err := time.ParseDuration(raw)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
result.Timeout = value
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func mergeConfig(base, override Config) Config {
|
|
result := base
|
|
if override.ListenAddr != "" {
|
|
result.ListenAddr = override.ListenAddr
|
|
}
|
|
|
|
if override.ServerURL != "" {
|
|
result.ServerURL = override.ServerURL
|
|
}
|
|
|
|
if override.StatsToken != "" {
|
|
result.StatsToken = override.StatsToken
|
|
}
|
|
|
|
if override.Timeout != 0 {
|
|
result.Timeout = override.Timeout
|
|
}
|
|
|
|
if override.TLSSkipVerify {
|
|
result.TLSSkipVerify = override.TLSSkipVerify
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func readStatsTokenFile(fileName string) (string, error) {
|
|
bytes, err := os.ReadFile(fileName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strings.TrimSuffix(string(bytes), "\n"), nil
|
|
}
|