diff --git a/.github/workflows/build-snapshot.yml b/.github/workflows/build-snapshot.yml index f0fd160..640ebd5 100755 --- a/.github/workflows/build-snapshot.yml +++ b/.github/workflows/build-snapshot.yml @@ -1,5 +1,7 @@ name: build-snapshot + +# todo: replace with production build later on: push: branches: [master] @@ -10,7 +12,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 - - name: Run GoReleaser uses: goreleaser/goreleaser-action@v5 with: diff --git a/.github/workflows/pre-commit-lint.yml b/.github/workflows/pre-commit-lint.yml deleted file mode 100755 index 027fd63..0000000 --- a/.github/workflows/pre-commit-lint.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: pre-commit-lint - -on: - pull_request: - branches: - - master - push: - branches: [master] - -jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v3 - - uses: actions/setup-go@v5 - - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/test-and-lint.yml b/.github/workflows/test-and-lint.yml new file mode 100755 index 0000000..0992ec9 --- /dev/null +++ b/.github/workflows/test-and-lint.yml @@ -0,0 +1,28 @@ +name: test-and-lint + +on: + pull_request: + branches: + - master + push: + branches: [master] + +jobs: + test-and-lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v3 + - uses: actions/setup-go@v5 + + - name: Run pre-commit with linters + uses: pre-commit/action@v3.0.1 + + - name: Run tests + run: go test -json > TestResults.json + + - name: Upload Go test results + uses: actions/upload-artifact@v4 + with: + name: Upload Go results + path: TestResults.json diff --git a/config.example.yml b/config.example.yml index d365bc5..54a0bb0 100644 --- a/config.example.yml +++ b/config.example.yml @@ -9,5 +9,5 @@ groups: group1: username: group1_user password: group1_pass -# loglevel can be one of "debug", "info", "warn", "error", or "fatal" -# loglevel: info +# loglevel can be one of "debug", "info", "warn", "error" +loglevel: info diff --git a/config.go b/config.go index e73a38a..8494de5 100755 --- a/config.go +++ b/config.go @@ -17,7 +17,7 @@ type Config struct { type SafeConfig struct { sync.RWMutex - C *Config + Config *Config } type HostConfig struct { @@ -25,19 +25,29 @@ type HostConfig struct { Password string `yaml:"password"` } -func (sc *SafeConfig) ReloadConfig(configFile string) error { - var c = &Config{} - +// Read exporter config from file +func NewConfigFromFile(configFile string) (*Config, error) { + var config = &Config{} yamlFile, err := os.ReadFile(configFile) if err != nil { - return err + return nil, err + } + + if err := yaml.Unmarshal(yamlFile, config); err != nil { + return nil, err } - if err := yaml.Unmarshal(yamlFile, c); err != nil { + + return config, nil +} + +func (sc *SafeConfig) ReloadConfig(configFile string) error { + var c, err = NewConfigFromFile(configFile) + if err != nil { return err } sc.Lock() - sc.C = c + sc.Config = c sc.Unlock() return nil @@ -46,13 +56,13 @@ func (sc *SafeConfig) ReloadConfig(configFile string) error { func (sc *SafeConfig) HostConfigForTarget(target string) (*HostConfig, error) { sc.Lock() defer sc.Unlock() - if hostConfig, ok := sc.C.Hosts[target]; ok { + if hostConfig, ok := sc.Config.Hosts[target]; ok { return &HostConfig{ Username: hostConfig.Username, Password: hostConfig.Password, }, nil } - if hostConfig, ok := sc.C.Hosts["default"]; ok { + if hostConfig, ok := sc.Config.Hosts["default"]; ok { return &HostConfig{ Username: hostConfig.Username, Password: hostConfig.Password, @@ -66,7 +76,7 @@ func (sc *SafeConfig) HostConfigForTarget(target string) (*HostConfig, error) { func (sc *SafeConfig) HostConfigForGroup(group string) (*HostConfig, error) { sc.Lock() defer sc.Unlock() - if hostConfig, ok := sc.C.Groups[group]; ok { + if hostConfig, ok := sc.Config.Groups[group]; ok { return &hostConfig, nil } return &HostConfig{}, fmt.Errorf("no credentials found for group %s", group) @@ -75,7 +85,7 @@ func (sc *SafeConfig) HostConfigForGroup(group string) (*HostConfig, error) { func (sc *SafeConfig) AppLogLevel() string { sc.Lock() defer sc.Unlock() - logLevel := sc.C.Loglevel + logLevel := sc.Config.Loglevel if logLevel != "" { return logLevel } diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..0fac742 --- /dev/null +++ b/config_test.go @@ -0,0 +1,19 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfigFromFile(t *testing.T) { + configFile := "config.example.yml" + + config, err := NewConfigFromFile(configFile) + assert.NoError(t, err) + assert.NotNil(t, config) + + assert.Equal(t, "info", config.Loglevel) + assert.Equal(t, config.Hosts["default"], HostConfig{Username: "user", Password: "pass"}) + assert.Equal(t, config.Groups["group1"], HostConfig{Username: "group1_user", Password: "group1_pass"}) +} diff --git a/main.go b/main.go index a52efcc..ceb711a 100755 --- a/main.go +++ b/main.go @@ -20,12 +20,6 @@ import ( ) var ( - Version string - BuildRevision string - BuildBranch string - BuildTime string - BuildHost string - webConfig = flag.String("web.config", "", "Path to web configuration file.") configFile = flag.String("config.file", "config.yml", "Path to configuration file.") listenAddress = flag.String( @@ -33,8 +27,8 @@ var ( ":9610", "Address to listen on for web interface and telemetry.", ) - sc = &SafeConfig{ - C: &Config{}, + config = &SafeConfig{ + Config: &Config{}, } reloadCh chan chan error ) @@ -43,7 +37,7 @@ func reloadHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" || r.Method == "PUT" { slog.Info("Triggered configuration reload from /-/reload HTTP endpoint") - err := sc.ReloadConfig(*configFile) + err := config.ReloadConfig(*configFile) if err != nil { slog.Error("failed to reload config file", slog.Any("error", err)) http.Error(w, "failed to reload config file", http.StatusInternalServerError) @@ -84,7 +78,7 @@ func metricsHandler() http.HandlerFunc { if ok && len(group[0]) >= 1 { // Trying to get hostConfig from group. - if hostConfig, err = sc.HostConfigForGroup(group[0]); err != nil { + if hostConfig, err = config.HostConfigForGroup(group[0]); err != nil { slog.Error("error getting credentials", slog.Any("error", err)) return } @@ -92,7 +86,7 @@ func metricsHandler() http.HandlerFunc { // Always falling back to single host config when group config failed. if hostConfig == nil { - if hostConfig, err = sc.HostConfigForTarget(target); err != nil { + if hostConfig, err = config.HostConfigForTarget(target); err != nil { slog.Error("error getting credentials", slog.Any("error", err)) return } @@ -107,20 +101,25 @@ func metricsHandler() http.HandlerFunc { // Delegate http serving to Prometheus client library, which will call collector.Collect. h := promhttp.HandlerFor(gatherers, promhttp.HandlerOpts{}) h.ServeHTTP(w, r) - } } // Parse the log leven from input func parseLogLevel(level string) slog.Level { ret := slog.LevelInfo - if level == "debug" { + switch level { + case "debug": ret = slog.LevelDebug - } else if level == "warning" { + case "info": + ret = slog.LevelInfo + case "warn": ret = slog.LevelWarn - } else if level == "error" { + case "error": ret = slog.LevelError + default: + slog.Warn("Invalid loglevel provided. Fallback to default") } + return ret } @@ -132,14 +131,14 @@ func main() { kitlogger := kitlog.NewLogfmtLogger(os.Stderr) // load config first time - if err := sc.ReloadConfig(*configFile); err != nil { + if err := config.ReloadConfig(*configFile); err != nil { slog.Error("Error parsing config file", slog.Any("error", err)) panic(err) } // Setup dinal logger from config opts := &slog.HandlerOptions{ - Level: parseLogLevel(sc.C.Loglevel), + Level: parseLogLevel(config.Config.Loglevel), } logger := slog.New(slog.NewTextHandler(os.Stdout, opts)) slog.SetDefault(logger) @@ -155,13 +154,13 @@ func main() { for { select { case <-hup: - if err := sc.ReloadConfig(*configFile); err != nil { + if err := config.ReloadConfig(*configFile); err != nil { slog.Error("failed to reload config file", slog.Any("error", err)) break } slog.Info("config file reload", slog.String("operation", "sc.ReloadConfig")) case rc := <-reloadCh: - if err := sc.ReloadConfig(*configFile); err != nil { + if err := config.ReloadConfig(*configFile); err != nil { slog.Error("failed to reload config file", slog.Any("error", err)) rc <- err break diff --git a/main_test.go b/main_test.go index 46b0cd7..95f5ca4 100644 --- a/main_test.go +++ b/main_test.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log/slog" "testing" @@ -14,13 +15,13 @@ func TestParseLogLevel(t *testing.T) { }{ {"debug", slog.LevelDebug}, {"info", slog.LevelInfo}, - {"warning", slog.LevelWarn}, + {"warn", slog.LevelWarn}, {"error", slog.LevelError}, {"unknown", slog.LevelInfo}, // default level } for _, tc := range testCases { actual := parseLogLevel(tc.level) - assert.Equal(t, tc.expected, actual, "Unexpected log level") + assert.Equal(t, tc.expected, actual, fmt.Sprintf("Unexpected log level parsed for infot %s", tc.level)) } }