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

chore: simply config #159

Merged
merged 3 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
52 changes: 52 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -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 {
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
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
}
77 changes: 77 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
80 changes: 3 additions & 77 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import (
"bufio"
"context"
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"net/url"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
Expand All @@ -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
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
// 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)
}
Expand Down
86 changes: 0 additions & 86 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
Loading