Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/file loader #18

Merged
merged 13 commits into from
Nov 27, 2023
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [Usage](#usage)
- [Configuration](#configuration)
- [Startup](#startup)
- [Loader](#loader)
- [Runtime](#runtime)
- [Check: Health](#check-health)
- [API](#api)
Expand Down Expand Up @@ -71,6 +72,18 @@ Priority of configuration (high to low):
3. Defined configuration file
4. Default configuration file

#### Loader

The loader component of the `sparrow` will load the [Runtime](#runtime) configuration dynamically.

The loader can be selected by specifying the `loaderType` configuration parameter.

The default loader is an `http` loader that is able to get the runtime configuration from a remote endpoint.

Available loader:
- `http`: The default. Loads configuration from a remote endpoint. Token authentication is available. Additional configuration parameter have the prefix `loaderHttp`.
- `file` (experimental): Loads configuration once from a local file. Additional configuration parameter have the prefix `loaderFile`. This is just for development purposes.

### Runtime

Besides the technical startup configuration the configuration for the `sparrow` checks is loaded dynamically from an http endpoint. The `loader` is able to load the configuration dynamically during the runtime. Checks can be enabled, disabled and configured. The available loader confutation options for the startup configuration can be found in [here](sparrow_run.md)
Expand Down
12 changes: 9 additions & 3 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package cmd
import (
"context"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/caas-team/sparrow/internal/logger"
"github.com/caas-team/sparrow/pkg/config"
"github.com/caas-team/sparrow/pkg/sparrow"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// NewCmdRun creates a new run command
Expand All @@ -21,6 +22,7 @@ func NewCmdRun() *cobra.Command {
LoaderHttpTimeout: "loaderHttpTimeout",
LoaderHttpRetryCount: "loaderHttpRetryCount",
LoaderHttpRetryDelay: "loaderHttpRetryDelay",
LoaderFilePath: "loaderFilePath",
}

cmd := &cobra.Command{
Expand All @@ -32,13 +34,15 @@ func NewCmdRun() *cobra.Command {

cmd.PersistentFlags().String(flagMapping.ApiListeningAddress, ":8080", "api: The address the server is listening on")

cmd.PersistentFlags().StringP(flagMapping.LoaderType, "l", "http", "defines the loader type that will load the checks configuration during the runtime")
cmd.PersistentFlags().StringP(flagMapping.LoaderType, "l", "http",
"defines the loader type that will load the checks configuration during the runtime. The fallback is the fileLoader")
cmd.PersistentFlags().Int(flagMapping.LoaderInterval, 300, "defines the interval the loader reloads the configuration in seconds")
cmd.PersistentFlags().String(flagMapping.LoaderHttpUrl, "", "http loader: The url where to get the remote configuration")
cmd.PersistentFlags().String(flagMapping.LoaderHttpToken, "", "http loader: Bearer token to authenticate the http endpoint")
cmd.PersistentFlags().Int(flagMapping.LoaderHttpTimeout, 30, "http loader: The timeout for the http request in seconds")
cmd.PersistentFlags().Int(flagMapping.LoaderHttpRetryCount, 3, "http loader: Amount of retries trying to load the configuration")
cmd.PersistentFlags().Int(flagMapping.LoaderHttpRetryDelay, 1, "http loader: The initial delay between retries in seconds")
cmd.PersistentFlags().String(flagMapping.LoaderFilePath, "config.yaml", "file loader: The path to the file to read the runtime config from")

viper.BindPFlag(flagMapping.ApiListeningAddress, cmd.PersistentFlags().Lookup(flagMapping.ApiListeningAddress))

Expand All @@ -49,6 +53,7 @@ func NewCmdRun() *cobra.Command {
viper.BindPFlag(flagMapping.LoaderHttpTimeout, cmd.PersistentFlags().Lookup(flagMapping.LoaderHttpTimeout))
viper.BindPFlag(flagMapping.LoaderHttpRetryCount, cmd.PersistentFlags().Lookup(flagMapping.LoaderHttpRetryCount))
viper.BindPFlag(flagMapping.LoaderHttpRetryDelay, cmd.PersistentFlags().Lookup(flagMapping.LoaderHttpRetryDelay))
viper.BindPFlag(flagMapping.LoaderFilePath, cmd.PersistentFlags().Lookup(flagMapping.LoaderFilePath))

return cmd
}
Expand All @@ -70,6 +75,7 @@ func run(fm *config.RunFlagsNameMapping) func(cmd *cobra.Command, args []string)
cfg.SetLoaderHttpTimeout(viper.GetInt(fm.LoaderHttpTimeout))
cfg.SetLoaderHttpRetryCount(viper.GetInt(fm.LoaderHttpRetryCount))
cfg.SetLoaderHttpRetryDelay(viper.GetInt(fm.LoaderHttpRetryDelay))
cfg.SetLoaderFilePath(viper.GetString(fm.LoaderFilePath))

if err := cfg.Validate(ctx, fm); err != nil {
log.Error("Error while validating the config", "error", err)
Expand Down
6 changes: 3 additions & 3 deletions pkg/checks/checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import (
"context"
"time"

"github.com/caas-team/sparrow/pkg/api"
"github.com/getkin/kin-openapi/openapi3"

"github.com/caas-team/sparrow/pkg/api"
)

// Available Checks will be registered in this map
// The key is the name of the Check
// The name needs to map the configuration item key
var RegisteredChecks = map[string]func() Check{
"rtt": GetRoundtripCheck,
"health": GetHealthCheck,
"health": NewHealthCheck,
}

//go:generate moq -out checks_moq.go . Check
Expand Down
2 changes: 1 addition & 1 deletion pkg/checks/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type Target struct {
}

// Constructor for the HealthCheck
func GetHealthCheck() Check {
func NewHealthCheck() Check {
return &Health{
route: "health",
}
Expand Down
90 changes: 0 additions & 90 deletions pkg/checks/roundtrip.go

This file was deleted.

9 changes: 9 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type LoaderConfig struct {
Type string
Interval time.Duration
http HttpLoaderConfig
file FileLoaderConfig
}

// HttpLoaderConfig is the configuration
Expand All @@ -33,6 +34,10 @@ type HttpLoaderConfig struct {
retryCfg helper.RetryConfig
}

type FileLoaderConfig struct {
path string
}

// NewConfig creates a new Config
func NewConfig() *Config {
return &Config{
Expand All @@ -49,6 +54,10 @@ func (c *Config) SetLoaderType(loaderType string) {
c.Loader.Type = loaderType
}

func (c *Config) SetLoaderFilePath(loaderFilePath string) {
c.Loader.file.path = loaderFilePath
}

// SetLoaderInterval sets the loader interval
// loaderInterval in seconds
func (c *Config) SetLoaderInterval(loaderInterval int) {
Expand Down
44 changes: 44 additions & 0 deletions pkg/config/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package config

import (
"context"
"os"

"gopkg.in/yaml.v3"

"github.com/caas-team/sparrow/internal/logger"
)

var _ Loader = (*FileLoader)(nil)

type FileLoader struct {
path string
c chan<- map[string]any
}

func NewFileLoader(cfg *Config, cCfgChecks chan<- map[string]any) *FileLoader {
return &FileLoader{
path: cfg.Loader.file.path,
c: cCfgChecks,
}
}

func (f *FileLoader) Run(ctx context.Context) {
log := logger.FromContext(ctx).WithGroup("FileLoader")
log.Info("Reading config from file", "file", f.path)
// TODO refactor this to use fs.FS
b, err := os.ReadFile(f.path)
if err != nil {
log.Error("Failed to read config file", "path", f.path, "error", err)
panic("failed to read config file " + err.Error())
}

var cfg RuntimeConfig

if err := yaml.Unmarshal(b, &cfg); err != nil {
log.Error("Failed to parse config file", "error", err)
panic("failed to parse config file: " + err.Error())
}

f.c <- cfg.Checks
}
59 changes: 59 additions & 0 deletions pkg/config/file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package config

import (
"context"
"reflect"
"testing"
)

func TestNewFileLoader(t *testing.T) {
l := NewFileLoader(&Config{Loader: LoaderConfig{file: FileLoaderConfig{path: "config.yaml"}}}, make(chan<- map[string]any))

if l.path != "config.yaml" {
t.Errorf("Expected path to be config.yaml, got %s", l.path)
}
if l.c == nil {
t.Errorf("Expected channel to be not nil")
}
}

func TestFileLoader_Run(t *testing.T) {
type fields struct {
path string
c chan map[string]any
}
type args struct {
ctx *context.Context
cancel *context.CancelFunc
}
type want struct {
cfg map[string]any
}
tests := []struct {
name string
fields fields
args args
want want
}{
{name: "Loads config from file", fields: fields{path: "testdata/config.yaml", c: make(chan map[string]any)}, args: func() args {
ctx, cancel := context.WithCancel(context.Background())
return args{ctx: &ctx, cancel: &cancel}
}(), want: want{cfg: map[string]any{"testCheck1": map[string]any{"enabled": true}}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f := &FileLoader{
path: tt.fields.path,
c: tt.fields.c,
}
go f.Run(*tt.args.ctx)
(*tt.args.cancel)()

config := <-tt.fields.c

if !reflect.DeepEqual(config, tt.want.cfg) {
t.Errorf("Expected config to be %v, got %v", tt.want.cfg, config)
}
})
}
}
1 change: 1 addition & 0 deletions pkg/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ type RunFlagsNameMapping struct {
LoaderHttpTimeout string
LoaderHttpRetryCount string
LoaderHttpRetryDelay string
LoaderFilePath string
}
7 changes: 6 additions & 1 deletion pkg/config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,10 @@ type Loader interface {

// Get a new typed runtime configuration loader
func NewLoader(cfg *Config, cCfgChecks chan<- map[string]any) Loader {
return NewHttpLoader(cfg, cCfgChecks)
switch cfg.Loader.Type {
case "http":
return NewHttpLoader(cfg, cCfgChecks)
default:
return NewFileLoader(cfg, cCfgChecks)
}
}
22 changes: 12 additions & 10 deletions pkg/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,18 @@ func (c *Config) Validate(ctx context.Context, fm *RunFlagsNameMapping) error {
log := logger.FromContext(ctx)

ok := true
if _, err := url.ParseRequestURI(c.Loader.http.url); err != nil {
ok = false
log.ErrorContext(ctx, "The loader http url is not a valid url",
fm.LoaderHttpUrl, c.Loader.http.url)
}

if c.Loader.http.retryCfg.Count < 0 || c.Loader.http.retryCfg.Count >= 5 {
ok = false
log.Error("The amount of loader http retries should be above 0 and below 6",
fm.LoaderHttpRetryCount, c.Loader.http.retryCfg.Count)
switch c.Loader.Type {
case "http":
if _, err := url.ParseRequestURI(c.Loader.http.url); err != nil {
ok = false
log.ErrorContext(ctx, "The loader http url is not a valid url",
fm.LoaderHttpUrl, c.Loader.http.url)
}
if c.Loader.http.retryCfg.Count < 0 || c.Loader.http.retryCfg.Count >= 5 {
ok = false
log.Error("The amount of loader http retries should be above 0 and below 6",
fm.LoaderHttpRetryCount, c.Loader.http.retryCfg.Count)
}
}

if !ok {
Expand Down
Loading