From d33c1e3b22deb64243a336d8d37f7592af8407bb Mon Sep 17 00:00:00 2001 From: Andrei Aaron Date: Thu, 15 Jan 2026 20:02:15 +0200 Subject: [PATCH] fix: now attempt to bind to the zot server socket to check if the server is running (#3703) Signed-off-by: Andrei Aaron --- pkg/cli/server/verify_retention.go | 22 +++++------- pkg/cli/server/verify_retention_test.go | 45 +++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/pkg/cli/server/verify_retention.go b/pkg/cli/server/verify_retention.go index 4b74b84b..a4b256be 100644 --- a/pkg/cli/server/verify_retention.go +++ b/pkg/cli/server/verify_retention.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net" - "net/http" "os" "os/signal" "syscall" @@ -208,27 +207,24 @@ func newVerifyFeatureRetentionCmd(conf *config.Config) *cobra.Command { // checkServerRunning checks if a Zot server is already running on the configured address/port. func checkServerRunning(conf *config.Config, logger zlog.Logger) error { - req, err := http.NewRequestWithContext(context.Background(), - http.MethodGet, - fmt.Sprintf("http://%s/v2", net.JoinHostPort(conf.HTTP.Address, conf.HTTP.Port)), - nil) + addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.HTTP.Address, conf.HTTP.Port)) if err != nil { - msg := "failed to create http request" + msg := "failed to resolve TCP address" logger.Error().Err(err).Msg(msg) return fmt.Errorf("%s: %w", msg, err) } - response, err := http.DefaultClient.Do(req) - if err == nil { - response.Body.Close() - logger.Warn().Err(zerr.ErrServerIsRunning). - Msg("server is running, in order to perform the verify-feature retention command the server should be shut down") + listener, err := net.ListenTCP("tcp", addr) + if err != nil { + msg := fmt.Sprintf("failed to bind to %s (server may be running or address unavailable)", addr.String()) + logger.Error().Err(err).Msg(msg) - return zerr.ErrServerIsRunning + return fmt.Errorf("%s: %w", msg, err) } - return nil + // Binding succeeded, server is not running + return listener.Close() } // isRemoteCacheEnabled checks if the remote cache is enabled for the global and subpaths storage configs. diff --git a/pkg/cli/server/verify_retention_test.go b/pkg/cli/server/verify_retention_test.go index 2850663c..fc761b09 100644 --- a/pkg/cli/server/verify_retention_test.go +++ b/pkg/cli/server/verify_retention_test.go @@ -154,15 +154,54 @@ func TestRetentionCheckNegative(t *testing.T) { os.Args = []string{"cli_test", "verify-feature", "retention", "-l", logFile, "-t", "30s", configFile} err := cli.NewServerRootCmd().Execute() So(err, ShouldNotBeNil) - So(err, ShouldEqual, zerr.ErrServerIsRunning) + // Check that error indicates binding failure (server is running) + So(err.Error(), ShouldContainSubstring, "failed to bind") - // Verify warning messages are logged to the log file + // Verify warning and error messages are logged to the log file logContent, err := os.ReadFile(logFile) So(err, ShouldBeNil) So(string(logContent), ShouldContainSubstring, "local storage detected - the zot server must be stopped to access the storage database") So(string(logContent), ShouldContainSubstring, - "server is running, in order to perform the verify-feature retention command the server should be shut down") + "failed to bind") + }) + + Convey("invalid address format", t, func(c C) { + testDir := t.TempDir() + logFile := MakeTempFilePath(t, "retention-check.log") + port := GetFreePort() + + // Use an invalid IPv6 address format that will pass LoadConfiguration + // but fail net.ResolveTCPAddr immediately (syntax error, no DNS lookup) + content := fmt.Sprintf(`{ + "distSpecVersion": "1.1.1", + "storage": { + "rootDirectory": "%s", + "gc": true + }, + "http": { + "address": "[invalid:ipv6", + "port": "%s" + }, + "log": { + "level": "debug" + } + }`, testDir, port) + configFile := MakeTempFileWithContent(t, "zot-config.json", content) + + os.Args = []string{"cli_test", "verify-feature", "retention", "-l", logFile, "-t", "30s", configFile} + err := cli.NewServerRootCmd().Execute() + So(err, ShouldNotBeNil) + // Check that error indicates TCP address resolution failure + So(err.Error(), ShouldContainSubstring, "failed to resolve TCP address") + + // Verify error message is logged to the log file + logContent, err := os.ReadFile(logFile) + So(err, ShouldBeNil) + So(string(logContent), ShouldContainSubstring, + "local storage detected - the zot server must be stopped to access the storage database") + So(string(logContent), ShouldContainSubstring, + "failed to resolve TCP address") }) Convey("invalid log-file flag", t, func(c C) {