From 915be62ca13c602243b8c1bf6581bc010bd30e24 Mon Sep 17 00:00:00 2001 From: ryan Date: Wed, 18 Mar 2026 10:38:45 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9Docker=E6=8C=82=E8=BD=BD=E6=BA=90=E7=9A=84=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=EF=BC=8C=E7=A1=AE=E4=BF=9D=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=92=8C=E7=9B=AE=E5=BD=95=E7=9A=84=E6=9C=89=E6=95=88?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openflare_agent/internal/nginx/manager.go | 58 +++++++++ .../internal/nginx/manager_test.go | 118 +++++++++++++++--- 2 files changed, 160 insertions(+), 16 deletions(-) diff --git a/openflare_agent/internal/nginx/manager.go b/openflare_agent/internal/nginx/manager.go index 0ecb2afb..063f654d 100644 --- a/openflare_agent/internal/nginx/manager.go +++ b/openflare_agent/internal/nginx/manager.go @@ -116,6 +116,9 @@ type DockerExecutor struct { func (e *DockerExecutor) Test(ctx context.Context) error { slog.Debug("running docker openresty test", "container", e.ContainerName, "image", e.Image) + if err := e.validateMountSources(); err != nil { + return err + } output, err := e.runEphemeralRuntimeCommand(ctx, "-t") if err != nil { return fmt.Errorf("docker %s -t failed: %w: %s", dockerRuntimeCommand, err, string(output)) @@ -182,6 +185,9 @@ func (e *DockerExecutor) removeContainer(ctx context.Context) error { func (e *DockerExecutor) runContainer(ctx context.Context) error { slog.Info("starting docker openresty container", "container", e.ContainerName, "image", e.Image) + if err := e.validateMountSources(); err != nil { + return err + } runArgs := []string{ "run", "-d", "--name", e.ContainerName, @@ -202,6 +208,58 @@ func (e *DockerExecutor) runContainer(ctx context.Context) error { return nil } +func (e *DockerExecutor) validateMountSources() error { + if err := ensureRegularFile(e.MainConfigPath, "openresty main config"); err != nil { + return err + } + if err := ensureDirectory(e.RouteConfigDir, "openresty route config dir"); err != nil { + return err + } + if err := ensureDirectory(e.CertDir, "openresty cert dir"); err != nil { + return err + } + if err := ensureDirectory(e.LuaDir, "openresty lua dir"); err != nil { + return err + } + return nil +} + +func ensureRegularFile(path string, label string) error { + cleanPath := strings.TrimSpace(path) + if cleanPath == "" { + return fmt.Errorf("%s path is empty", label) + } + info, err := os.Stat(cleanPath) + if err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("%s %q does not exist; run a config apply first so Docker does not create a directory mount source", label, cleanPath) + } + return fmt.Errorf("stat %s %q failed: %w", label, cleanPath, err) + } + if info.IsDir() { + return fmt.Errorf("%s %q is a directory; expected a file for Docker bind mount", label, cleanPath) + } + return nil +} + +func ensureDirectory(path string, label string) error { + cleanPath := strings.TrimSpace(path) + if cleanPath == "" { + return fmt.Errorf("%s path is empty", label) + } + info, err := os.Stat(cleanPath) + if err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("%s %q does not exist; expected a directory for Docker bind mount", label, cleanPath) + } + return fmt.Errorf("stat %s %q failed: %w", label, cleanPath, err) + } + if !info.IsDir() { + return fmt.Errorf("%s %q is not a directory; expected a directory for Docker bind mount", label, cleanPath) + } + return nil +} + type Manager struct { MainConfigPath string RouteConfigPath string diff --git a/openflare_agent/internal/nginx/manager_test.go b/openflare_agent/internal/nginx/manager_test.go index a6f21f1b..85b0744b 100644 --- a/openflare_agent/internal/nginx/manager_test.go +++ b/openflare_agent/internal/nginx/manager_test.go @@ -133,7 +133,27 @@ func TestDockerExecutorCheckHealthFailsWhenContainerStopped(t *testing.T) { } } +func prepareDockerMountSources(t *testing.T) (string, string, string, string) { + t.Helper() + tempDir := t.TempDir() + mainConfigPath := filepath.Join(tempDir, "nginx.conf") + routeConfigDir := filepath.Join(tempDir, "conf.d") + certDir := filepath.Join(tempDir, "certs") + luaDir := filepath.Join(tempDir, "lua") + + if err := os.WriteFile(mainConfigPath, []byte("events {}\nhttp {}\n"), 0o644); err != nil { + t.Fatalf("WriteFile failed: %v", err) + } + for _, dir := range []string{routeConfigDir, certDir, luaDir} { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("MkdirAll failed: %v", err) + } + } + return mainConfigPath, routeConfigDir, certDir, luaDir +} + func TestDockerExecutorStartsContainerWhenMissing(t *testing.T) { + mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t) runner := &fakeRunner{ runFn: func(name string, args ...string) ([]byte, error) { if len(args) >= 1 && args[0] == "inspect" { @@ -146,11 +166,11 @@ func TestDockerExecutorStartsContainerWhenMissing(t *testing.T) { DockerBinary: "docker", ContainerName: "openflare-openresty", Image: "openresty/openresty:alpine", - MainConfigPath: filepath.Clean("/tmp/nginx.conf"), - RouteConfigDir: filepath.Clean("/tmp/routes"), - CertDir: filepath.Clean("/tmp/certs"), + MainConfigPath: mainConfigPath, + RouteConfigDir: routeConfigDir, + CertDir: certDir, NginxCertDir: "/etc/nginx/openflare-certs", - LuaDir: filepath.Clean("/tmp/lua"), + LuaDir: luaDir, NginxLuaDir: "/etc/nginx/openflare-lua", Runner: runner, } @@ -171,6 +191,7 @@ func TestDockerExecutorStartsContainerWhenMissing(t *testing.T) { } func TestDockerExecutorStartsStoppedContainer(t *testing.T) { + mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t) runner := &fakeRunner{ runFn: func(name string, args ...string) ([]byte, error) { if len(args) >= 2 && args[0] == "inspect" { @@ -183,11 +204,11 @@ func TestDockerExecutorStartsStoppedContainer(t *testing.T) { DockerBinary: "docker", ContainerName: "openflare-openresty", Image: "openresty/openresty:alpine", - MainConfigPath: filepath.Clean("/tmp/nginx.conf"), - RouteConfigDir: filepath.Clean("/tmp/routes"), - CertDir: filepath.Clean("/tmp/certs"), + MainConfigPath: mainConfigPath, + RouteConfigDir: routeConfigDir, + CertDir: certDir, NginxCertDir: "/etc/nginx/openflare-certs", - LuaDir: filepath.Clean("/tmp/lua"), + LuaDir: luaDir, NginxLuaDir: "/etc/nginx/openflare-lua", Runner: runner, } @@ -211,10 +232,7 @@ func TestDockerExecutorStartsStoppedContainer(t *testing.T) { } func TestDockerExecutorRunContainerMountsManagedFiles(t *testing.T) { - mainConfigPath := filepath.Clean("/tmp/managed/nginx.conf") - routeConfigDir := filepath.Clean("/tmp/managed/conf.d") - certDir := filepath.Clean("/tmp/managed/certs") - luaDir := filepath.Clean("/tmp/managed/lua") + mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t) runner := &fakeRunner{} executor := &DockerExecutor{ DockerBinary: "docker", @@ -256,6 +274,7 @@ func TestDockerExecutorRunContainerMountsManagedFiles(t *testing.T) { } func TestDockerExecutorRecreatesContainerOnStartup(t *testing.T) { + mainConfigPath, routeConfigDir, certDir, luaDir := prepareDockerMountSources(t) runner := &fakeRunner{ runFn: func(name string, args ...string) ([]byte, error) { if len(args) >= 1 && args[0] == "inspect" { @@ -268,11 +287,11 @@ func TestDockerExecutorRecreatesContainerOnStartup(t *testing.T) { DockerBinary: "docker", ContainerName: "openflare-openresty", Image: "openresty/openresty:alpine", - MainConfigPath: filepath.Clean("/tmp/nginx.conf"), - RouteConfigDir: filepath.Clean("/tmp/routes"), - CertDir: filepath.Clean("/tmp/certs"), + MainConfigPath: mainConfigPath, + RouteConfigDir: routeConfigDir, + CertDir: certDir, NginxCertDir: "/etc/nginx/openflare-certs", - LuaDir: filepath.Clean("/tmp/lua"), + LuaDir: luaDir, NginxLuaDir: "/etc/nginx/openflare-lua", OpenrestyObservabilityPort: 18081, Runner: runner, @@ -292,6 +311,73 @@ func TestDockerExecutorRecreatesContainerOnStartup(t *testing.T) { } } +func TestDockerExecutorRunContainerRejectsMissingMainConfigFile(t *testing.T) { + tempDir := t.TempDir() + routeConfigDir := filepath.Join(tempDir, "conf.d") + certDir := filepath.Join(tempDir, "certs") + luaDir := filepath.Join(tempDir, "lua") + for _, dir := range []string{routeConfigDir, certDir, luaDir} { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("MkdirAll failed: %v", err) + } + } + + executor := &DockerExecutor{ + DockerBinary: "docker", + ContainerName: "openflare-openresty", + Image: "openresty/openresty:alpine", + MainConfigPath: filepath.Join(tempDir, "nginx.conf"), + RouteConfigDir: routeConfigDir, + CertDir: certDir, + NginxCertDir: "/etc/nginx/openflare-certs", + LuaDir: luaDir, + NginxLuaDir: "/etc/nginx/openflare-lua", + Runner: &fakeRunner{}, + } + + err := executor.runContainer(context.Background()) + if err == nil { + t.Fatal("expected missing main config file to be rejected") + } + if !strings.Contains(err.Error(), "run a config apply first") { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestDockerExecutorRunContainerRejectsMainConfigDirectory(t *testing.T) { + tempDir := t.TempDir() + mainConfigPath := filepath.Join(tempDir, "nginx.conf") + routeConfigDir := filepath.Join(tempDir, "conf.d") + certDir := filepath.Join(tempDir, "certs") + luaDir := filepath.Join(tempDir, "lua") + for _, dir := range []string{mainConfigPath, routeConfigDir, certDir, luaDir} { + if err := os.MkdirAll(dir, 0o755); err != nil { + t.Fatalf("MkdirAll failed: %v", err) + } + } + + executor := &DockerExecutor{ + DockerBinary: "docker", + ContainerName: "openflare-openresty", + Image: "openresty/openresty:alpine", + MainConfigPath: mainConfigPath, + RouteConfigDir: routeConfigDir, + CertDir: certDir, + NginxCertDir: "/etc/nginx/openflare-certs", + LuaDir: luaDir, + NginxLuaDir: "/etc/nginx/openflare-lua", + Runner: &fakeRunner{}, + } + + err := executor.runContainer(context.Background()) + if err == nil { + t.Fatal("expected main config directory to be rejected") + } + if !strings.Contains(err.Error(), "expected a file") { + t.Fatalf("unexpected error: %v", err) + } +} + func TestNewExecutorUsesAbsoluteDockerMountPath(t *testing.T) { executor := NewExecutor(ExecutorOptions{ DockerBinary: "docker",