Traefik v3.7.17 (GHSA-xv5x-242w-7hf7); R-1347 a restore forgets the update-held flag; R-1348 a failed put-back at start-up mails; R-1339, R-1351, R-1323 truthful start-up lines and one lock over FileBrowser's compose. Commit only.
gates / gates (push) Successful in 29s

This commit is contained in:
admin committed 2026-11-02 23:14:16 +01:00
1 parent a90b62323f
commit c4b6844d34
19 files changed
+569 -9

No files matched your search

@@ -0,0 +1,78 @@
package stacks
import (
"bytes"
"errors"
"log"
"net/http/httptest"
"strings"
"testing"
)
// R-1323: the checked FileBrowser container can be replaced between the check and the leave (the image moved at start).
// „No such container" then means the OLD one is gone, not that every app still reaches FileBrowser. The line reads the
// CURRENT container's networks and says what is true; a current container that IS on the app network keeps the alarm.
//
// RED-PROOF (2026-11-02): before the fix the first case logged „could not leave traefik-public — every app can still reach
// it … No such container" (seen on 9202, 0.346.1-rc.1, 2026-10-27), and this fails on that line.
func r1323Rig(t *testing.T, currentOnApps bool) (*Manager, *bytes.Buffer) {
t.Helper()
fb := &fakeFB{password: "admin"}
srv := httptest.NewServer(fb.handler())
t.Cleanup(srv.Close)
m, _, _ := fbManager(t, srv.URL)
var buf bytes.Buffer
m.logger = log.New(&buf, "", 0)
runs := []string{"old111 2026-10-27T14:58:15Z"}
m.fbStarted = func() string {
r := runs[0]
if len(runs) > 1 {
runs = runs[1:]
}
return r
}
nets := map[string]bool{"old111@traefik-public": true, "new222@traefik-public": currentOnApps}
m.fbNetOnFn = func(c, n string) bool { return nets[c+"@"+n] }
m.fbNetConnectFn = func(c, n string) error { nets[c+"@"+n] = true; return nil }
m.fbNetDisconnectFn = func(c, n string) error {
if c == "old111" {
runs = []string{"new222 2026-10-27T14:58:17Z"} // the recreate won the race
return errors.New("Error response from daemon: No such container: old111")
}
return nil
}
m.fbNetEnsureFn = func(string) error { return nil }
m.fbOfficeLinkedFn = func() (bool, bool) { return false, false }
return m, &buf
}
func TestR1323_AReplacedContainerIsNotReportedAsExposed(t *testing.T) {
m, buf := r1323Rig(t, false)
m.fileBrowserGateLocked()
got := buf.String()
if strings.Contains(got, "every app can still reach it") {
t.Fatalf("a false alarm for a container that is gone: %q", got)
}
if !strings.Contains(got, "new222") || !strings.Contains(got, "not on traefik-public") {
t.Fatalf("the line must name the current container and the truth: %q", got)
}
}
func TestR1323_ACurrentContainerOnTheAppNetworkKeepsTheAlarm(t *testing.T) {
m, buf := r1323Rig(t, true)
m.fileBrowserGateLocked()
got := buf.String()
if !strings.Contains(got, "every app can still reach it") || !strings.Contains(got, "new222") {
t.Fatalf("the replacement IS on the app network: the alarm stays, naming it: %q", got)
}
}
// Any other failure to leave keeps the alarm as before (control).
func TestR1323_AnotherDisconnectFailureKeepsTheAlarm(t *testing.T) {
m, buf := r1323Rig(t, false)
m.fbNetDisconnectFn = func(c, n string) error { return errors.New("daemon busy") }
m.fileBrowserGateLocked()
if got := buf.String(); !strings.Contains(got, "every app can still reach it") || !strings.Contains(got, "daemon busy") {
t.Fatalf("a real failure to leave must keep its alarm: %q", got)
}
}