From 8aef3245a25e1b6202f52d203b2a8da47df9b01b Mon Sep 17 00:00:00 2001 From: Steven Hartland Date: Mon, 30 Sep 2024 15:35:11 +0100 Subject: [PATCH] chore: simply config (#159) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: simply config Simplify the configuration so its easier to add new settings, clearer what the default values are and which environment variables impact the running of container. This includes compatibility with slog for future use. Bump version of golangci-lint so to prevent invalid failures * docs: fix version in readme --------- Co-authored-by: Manuel de la Peña --- README.md | 2 +- config.go | 52 ++++++++++++++++++++++++++++++ config_test.go | 77 ++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ main.go | 80 ++-------------------------------------------- main_test.go | 86 -------------------------------------------------- 7 files changed, 136 insertions(+), 164 deletions(-) create mode 100644 config.go create mode 100644 config_test.go diff --git a/README.md b/README.md index 6ed1cf3..306d021 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This project helps you to remove containers/networks/volumes/images by given fil $ RYUK_PORT=8080 ./bin/moby-ryuk $ # You can also run it with Docker - $ docker run -v /var/run/docker.sock:/var/run/docker.sock -e RYUK_PORT=8080 -p 8080:8080 testcontainers/ryuk:0.6.0 + $ docker run -v /var/run/docker.sock:/var/run/docker.sock -e RYUK_PORT=8080 -p 8080:8080 testcontainers/ryuk:0.9.0 1. Connect via TCP: diff --git a/config.go b/config.go new file mode 100644 index 0000000..2ae1556 --- /dev/null +++ b/config.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "log/slog" + "time" + + "github.com/caarlos0/env/v11" +) + +// config represents the configuration for the reaper. +type config struct { + // ConnectionTimeout is the duration without receiving any connections which will trigger a shutdown. + ConnectionTimeout time.Duration `env:"RYUK_CONNECTION_TIMEOUT" envDefault:"60s"` + + // ReconnectionTimeout is the duration after the last connection closes which will trigger + // resource clean up and shutdown. + ReconnectionTimeout time.Duration `env:"RYUK_RECONNECTION_TIMEOUT" envDefault:"10s"` + + // ShutdownTimeout is the maximum amount of time the reaper will wait + // for once signalled to shutdown before it terminates even if connections + // are still established. + ShutdownTimeout time.Duration `env:"RYUK_SHUTDOWN_TIMEOUT" envDefault:"10m"` + + // Port is the port to listen on for connections. + Port uint16 `env:"RYUK_PORT" envDefault:"8080"` + + // Verbose is whether to enable verbose aka debug logging. + Verbose bool `env:"RYUK_VERBOSE" envDefault:"false"` +} + +// LogAttrs returns the configuration as a slice of attributes. +func (c config) LogAttrs() []slog.Attr { + return []slog.Attr{ + slog.Duration("connection_timeout", c.ConnectionTimeout), + slog.Duration("reconnection_timeout", c.ReconnectionTimeout), + slog.Duration("shutdown_timeout", c.ShutdownTimeout), + slog.Int("port", int(c.Port)), + slog.Bool("verbose", c.Verbose), + } +} + +// loadConfig loads the configuration from the environment +// applying defaults where necessary. +func loadConfig() (*config, error) { + var cfg config + if err := env.Parse(&cfg); err != nil { + return nil, fmt.Errorf("parse env: %w", err) + } + + return &cfg, nil +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..e0ef6b4 --- /dev/null +++ b/config_test.go @@ -0,0 +1,77 @@ +package main + +import ( + "os" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// clearConfigEnv clears the environment variables for the config fields. +func clearConfigEnv(t *testing.T) { + t.Helper() + + var cfg config + typ := reflect.TypeOf(cfg) + for i := range typ.NumField() { + field := typ.Field(i) + if name := field.Tag.Get("env"); name != "" { + if os.Getenv(name) != "" { + t.Setenv(name, "") + } + } + } +} + +func Test_loadConfig(t *testing.T) { + clearConfigEnv(t) + + t.Run("defaults", func(t *testing.T) { + expected := config{ + Port: 8080, + ConnectionTimeout: time.Minute, + ReconnectionTimeout: time.Second * 10, + ShutdownTimeout: time.Minute * 10, + } + + cfg, err := loadConfig() + require.NoError(t, err) + require.Equal(t, expected, *cfg) + }) + + t.Run("custom", func(t *testing.T) { + t.Setenv("RYUK_PORT", "1234") + t.Setenv("RYUK_CONNECTION_TIMEOUT", "2s") + t.Setenv("RYUK_RECONNECTION_TIMEOUT", "3s") + t.Setenv("RYUK_SHUTDOWN_TIMEOUT", "7s") + t.Setenv("RYUK_VERBOSE", "true") + + expected := config{ + Port: 1234, + ConnectionTimeout: time.Second * 2, + ReconnectionTimeout: time.Second * 3, + ShutdownTimeout: time.Second * 7, + Verbose: true, + } + + cfg, err := loadConfig() + require.NoError(t, err) + require.Equal(t, expected, *cfg) + }) + + for _, name := range []string{ + "RYUK_PORT", + "RYUK_CONNECTION_TIMEOUT", + "RYUK_RECONNECTION_TIMEOUT", + "RYUK_SHUTDOWN_TIMEOUT", + "RYUK_VERBOSE", + } { + t.Run("invalid-"+name, func(t *testing.T) { + t.Setenv(name, "invalid") + _, err := loadConfig() + require.Error(t, err) + }) + } +} diff --git a/go.mod b/go.mod index 37cb344..4fa4551 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/testcontainers/moby-ryuk go 1.23 require ( + github.com/caarlos0/env/v11 v11.2.2 github.com/docker/docker v27.2.0+incompatible github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 diff --git a/go.sum b/go.sum index 70ba5cd..5cd15f0 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg= +github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= diff --git a/main.go b/main.go index 673e2d5..bcb433f 100644 --- a/main.go +++ b/main.go @@ -4,15 +4,12 @@ import ( "bufio" "context" "errors" - "flag" "fmt" "io" "log" "net" "net/url" - "os" "os/signal" - "strconv" "strings" "sync" "syscall" @@ -25,89 +22,18 @@ import ( ) const ( - connectionTimeoutEnv string = "RYUK_CONNECTION_TIMEOUT" - portEnv string = "RYUK_PORT" - reconnectionTimeoutEnv string = "RYUK_RECONNECTION_TIMEOUT" - ryukLabel string = "org.testcontainers.ryuk" - verboseEnv string = "RYUK_VERBOSE" + ryukLabel string = "org.testcontainers.ryuk" ) var ( - port int + port uint16 connectionTimeout time.Duration reconnectionTimeout time.Duration verbose bool ) -type config struct { - Port int - ConnectionTimeout time.Duration - ReconnectionTimeout time.Duration - Verbose bool -} - -// newConfig parses command line flags and returns a parsed config. config.timeout -// can be set by environment variable, RYUK_CONNECTION_TIMEOUT. If an error occurs -// while parsing RYUK_CONNECTION_TIMEOUT the error is returned. -func newConfig(args []string) (*config, error) { - cfg := config{ - Port: 8080, - ConnectionTimeout: 60 * time.Second, - ReconnectionTimeout: 10 * time.Second, - Verbose: false, - } - - fs := flag.NewFlagSet("ryuk", flag.ExitOnError) - fs.SetOutput(os.Stdout) - - fs.IntVar(&cfg.Port, "p", 8080, "Deprecated: please use the "+portEnv+" environment variable to set the port to bind at") - - err := fs.Parse(args) - if err != nil { - return nil, err - } - - if timeout, ok := os.LookupEnv(connectionTimeoutEnv); ok { - parsedTimeout, err := time.ParseDuration(timeout) - if err != nil { - return nil, fmt.Errorf("failed to parse \"%s\": %s", connectionTimeoutEnv, err) - } - - cfg.ConnectionTimeout = parsedTimeout - } - - if port, ok := os.LookupEnv(portEnv); ok { - parsedPort, err := strconv.Atoi(port) - if err != nil { - return nil, fmt.Errorf("failed to parse \"%s\": %s", portEnv, err) - } - - cfg.Port = parsedPort - } - - if timeout, ok := os.LookupEnv(reconnectionTimeoutEnv); ok { - parsedTimeout, err := time.ParseDuration(timeout) - if err != nil { - return nil, fmt.Errorf("failed to parse \"%s\": %s", reconnectionTimeoutEnv, err) - } - - cfg.ReconnectionTimeout = parsedTimeout - } - - if verbose, ok := os.LookupEnv(verboseEnv); ok { - v, err := strconv.ParseBool(verbose) - if err != nil { - return nil, fmt.Errorf("failed to parse \"%s\": %s", verboseEnv, err) - } - - cfg.Verbose = v - } - - return &cfg, nil -} - func main() { - cfg, err := newConfig(os.Args[1:]) + cfg, err := loadConfig() if err != nil { panic(err) } diff --git a/main_test.go b/main_test.go index f5a7a9e..8de2ca2 100644 --- a/main_test.go +++ b/main_test.go @@ -312,89 +312,3 @@ func TestPrune(t *testing.T) { assert.Equal(t, maxLength, di) }) } - -func Test_newConfig(t *testing.T) { - t.Run("should return an error when failing to parse RYUK_CONNECTION_TIMEOUT environment variable", func(t *testing.T) { - t.Setenv(connectionTimeoutEnv, "bad_value") - - config, err := newConfig([]string{}) - require.NotNil(t, err) - require.Nil(t, config) - }) - - t.Run("should set connectionTimeout with RYUK_CONNECTION_TIMEOUT environment variable", func(t *testing.T) { - t.Setenv(connectionTimeoutEnv, "10s") - - config, err := newConfig([]string{}) - require.Nil(t, err) - assert.Equal(t, 10*time.Second, config.ConnectionTimeout) - }) - - t.Run("should return an error when failing to parse RYUK_PORT environment variable", func(t *testing.T) { - t.Setenv(portEnv, "bad_value") - - config, err := newConfig([]string{}) - require.NotNil(t, err) - require.Nil(t, config) - }) - - t.Run("should set connectionTimeout with RYUK_PORT environment variable", func(t *testing.T) { - t.Setenv(portEnv, "8081") - - config, err := newConfig([]string{}) - require.Nil(t, err) - assert.Equal(t, 8081, config.Port) - }) - - t.Run("should return an error when failing to parse RYUK_RECONNECTION_TIMEOUT environment variable", func(t *testing.T) { - t.Setenv(reconnectionTimeoutEnv, "bad_value") - - config, err := newConfig([]string{}) - require.NotNil(t, err) - require.Nil(t, config) - }) - - t.Run("should set connectionTimeout with RYUK_RECONNECTION_TIMEOUT environment variable", func(t *testing.T) { - t.Setenv(reconnectionTimeoutEnv, "100s") - - config, err := newConfig([]string{}) - require.Nil(t, err) - assert.Equal(t, 100*time.Second, config.ReconnectionTimeout) - }) - - t.Run("should return an error when failing to parse RYUK_VERBOSE environment variable", func(t *testing.T) { - t.Setenv(verboseEnv, "bad_value") - - config, err := newConfig([]string{}) - require.NotNil(t, err) - require.Nil(t, config) - }) - - t.Run("should set verbose with RYUK_VERBOSE environment variable", func(t *testing.T) { - t.Setenv(verboseEnv, "true") - - config, err := newConfig([]string{}) - require.Nil(t, err) - assert.True(t, config.Verbose) - - t.Setenv(verboseEnv, "false") - - config, err = newConfig([]string{}) - require.Nil(t, err) - assert.False(t, config.Verbose) - }) - - t.Run("should set port with port flag", func(t *testing.T) { - config, err := newConfig([]string{"-p", "3000"}) - require.Nil(t, err) - assert.Equal(t, 3000, config.Port) - }) - - t.Run("should set port from env with port flag and RYUK_PORT environment variable", func(t *testing.T) { - t.Setenv(portEnv, "8081") - - config, err := newConfig([]string{"-p", "3000"}) - require.Nil(t, err) - assert.Equal(t, 8081, config.Port) - }) -}