-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A significant rewrite to ensure that we don't suffer from shutdown race conditions as the prune condition is met and additional resources are being created. Previously this would remove resources that were still in use, now we retry if we detect new resources have been created within a window of the prune condition triggering. This supports the following new environment configuration settings: - RYUK_REMOVE_RETRIES - The number of times to retry removing a resource. - RYUK_REQUEST_TIMEOUT - The timeout for any Docker requests. - RYUK_RETRY_OFFSET - The offset added to the start time of the prune pass that is used as the minimum resource creation time. - RYUK_SHUTDOWN_TIMEOUT - The duration after shutdown has been requested when the remaining connections are ignored and prune checks start. Also bumps go to v1.22 and golangci-lint to v1.59.1 to avoid false lint failures. Update README to correct example, as health is only valid for containers not the other resources, so would cause failures.
- Loading branch information
Showing
17 changed files
with
1,645 additions
and
904 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,9 @@ | |
|
||
vendor/ | ||
bin/ | ||
|
||
# Binary | ||
moby-ryuk | ||
|
||
# VS Code | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
run: | ||
timeout: 2m | ||
|
||
linters-settings: | ||
gosec: | ||
excludes: | ||
- G601 ## Implicit memory aliasing of items from a range statement - not possible in go 1.22. | ||
cyclop: | ||
max-complexity: 15 | ||
nestif: | ||
min-complexity: 10 | ||
govet: | ||
settings: | ||
shadow: | ||
strict: true | ||
enable-all: true | ||
nolintlint: | ||
require-explanation: true | ||
godot: | ||
scope: all | ||
|
||
linters: | ||
enable-all: true | ||
disable: | ||
# Spammy / low value | ||
- varnamelen | ||
- exhaustruct | ||
- nlreturn | ||
- wsl | ||
- lll | ||
- paralleltest | ||
# Duplicate functionality. | ||
- funlen | ||
- gocognit | ||
# Deprecated. | ||
- execinquery | ||
- gomnd | ||
# Good but gets in the way too often. | ||
- testpackage | ||
# Unknown details about how Artemis works are flagged with TODO's. | ||
- godox | ||
# Seems to be broken. | ||
- depguard | ||
# Makes it messy for multiple optional tags. | ||
- tagalign | ||
# Not needed for go 1.22+. | ||
- exportloopref | ||
- errchkjson # Duplicate functionality for errcheck. | ||
|
||
issues: | ||
include: | ||
- EXC0012 | ||
- EXC0014 | ||
exclude-rules: | ||
# Exclude linters which aren't an issue in tests. | ||
- path: _test\.go | ||
linters: | ||
- gochecknoglobals | ||
- wrapcheck | ||
|
||
# File mode permissions are fine for constants. | ||
- text: "Magic number: 0o\\d+" | ||
linters: | ||
- mnd | ||
|
||
# Field alignment in tests isn't a performance issue. | ||
- text: fieldalignment | ||
path: _test\.go | ||
|
||
# Dynamic errors can provide useful context. | ||
- text: "do not define dynamic errors, use wrapped static errors instead:" | ||
linters: | ||
- err113 | ||
|
||
# We need to use the `err` named return for error handling. | ||
- text: 'named return "err" with type "error" found' | ||
linters: | ||
- nonamedreturns | ||
|
||
# Interface casting is fine in mock. | ||
- path: mock_test\.go | ||
linters: | ||
- forcetypeassert |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
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"` | ||
|
||
// RequestTimeout is the timeout for any Docker requests. | ||
RequestTimeout time.Duration `env:"RYUK_REQUEST_TIMEOUT" envDefault:"10s"` | ||
|
||
// RemoveRetries is the number of times to retry removing a resource. | ||
RemoveRetries int `env:"RYUK_REMOVE_RETRIES" envDefault:"10"` | ||
|
||
// RetryOffset is the offset added to the start time of the prune pass that is | ||
// used as the minimum resource creation time. Any resource created after this | ||
// calculated time will trigger a retry to ensure in use resources are not removed. | ||
RetryOffset time.Duration `env:"RYUK_RETRY_OFFSET" envDefault:"-1s"` | ||
|
||
// 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("request_timeout", c.RequestTimeout), | ||
slog.Duration("shutdown_timeout", c.ShutdownTimeout), | ||
slog.Int("remove_retries", c.RemoveRetries), | ||
slog.Duration("retry_offset", c.RetryOffset), | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
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) { | ||
tests := map[string]struct { | ||
setEnv func(*testing.T) | ||
expected config | ||
}{ | ||
"defaults": { | ||
setEnv: clearConfigEnv, | ||
expected: config{ | ||
ConnectionTimeout: time.Minute, | ||
Port: 8080, | ||
ReconnectionTimeout: time.Second * 10, | ||
RemoveRetries: 10, | ||
RequestTimeout: time.Second * 10, | ||
RetryOffset: -time.Second, | ||
ShutdownTimeout: time.Minute * 10, | ||
}, | ||
}, | ||
"custom": { | ||
setEnv: func(t *testing.T) { | ||
t.Helper() | ||
|
||
clearConfigEnv(t) | ||
t.Setenv("RYUK_PORT", "1234") | ||
t.Setenv("RYUK_CONNECTION_TIMEOUT", "2s") | ||
t.Setenv("RYUK_RECONNECTION_TIMEOUT", "3s") | ||
t.Setenv("RYUK_REQUEST_TIMEOUT", "4s") | ||
t.Setenv("RYUK_REMOVE_RETRIES", "5") | ||
t.Setenv("RYUK_RETRY_OFFSET", "-6s") | ||
t.Setenv("RYUK_SHUTDOWN_TIMEOUT", "7s") | ||
}, | ||
expected: config{ | ||
Port: 1234, | ||
ConnectionTimeout: time.Second * 2, | ||
ReconnectionTimeout: time.Second * 3, | ||
RequestTimeout: time.Second * 4, | ||
RemoveRetries: 5, | ||
RetryOffset: -time.Second * 6, | ||
ShutdownTimeout: time.Second * 7, | ||
}, | ||
}, | ||
} | ||
for name, tc := range tests { | ||
t.Run(name, func(t *testing.T) { | ||
if tc.setEnv != nil { | ||
tc.setEnv(t) | ||
} | ||
|
||
cfg, err := loadConfig() | ||
require.NoError(t, err) | ||
require.Equal(t, tc.expected, *cfg) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package main | ||
|
||
const ( | ||
// labelBase is the base label for testcontainers. | ||
labelBase = "org.testcontainers" | ||
|
||
// ryukLabel is the label used to identify reaper containers. | ||
ryukLabel = labelBase + ".ryuk" | ||
|
||
// fieldError is the log field key for errors. | ||
fieldError = "error" | ||
|
||
// fieldAddress is the log field a client or listening address. | ||
fieldAddress = "address" | ||
|
||
// fieldClients is the log field used for client counts. | ||
fieldClients = "clients" | ||
) |
Oops, something went wrong.