Skip to content

Commit

Permalink
chore: simply config
Browse files Browse the repository at this point in the history
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
  • Loading branch information
stevenh committed Sep 7, 2024
1 parent e8ed9c9 commit e79c12c
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 165 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 # v4
with:
version: v1.55.2
version: v1.60.3
args: --timeout=3m
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 {
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)
})
}
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module github.com/testcontainers/moby-ryuk

go 1.21
go 1.23

require (
github.com/caarlos0/env/v11 v11.2.2
github.com/docker/docker v27.1.1+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
// 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)
})
}

0 comments on commit e79c12c

Please sign in to comment.