Keep deployment logs findable after the container is recreated (closes #214)
Check / check (pull_request) Skipped

Deployment log files were stored under a directory named after the
container's hostname, which Docker changes whenever the container is
recreated, so every older log download returned 404. Logs now live under
logs/<appname>/. The download handler still finds logs written by older
versions under any hostname directory.

Model: opus-5-5
This commit is contained in:
2026-09-23 09:08:50 +00:00
parent 19619b1cd2
commit 23649a7c60
4 changed files with 60 additions and 8 deletions
+29
View File
@@ -69,6 +69,35 @@ func TestHandleDeploymentLogDownloadServesLegitimateFile(t *testing.T) {
assert.Contains(t, recorder.Body.String(), "deploy log contents")
}
// TestHandleDeploymentLogDownloadServesLogFromOldHostnameDir verifies a
// log written by an older version, under the hostname of a container that
// has since been recreated, can still be downloaded.
func TestHandleDeploymentLogDownloadServesLogFromOldHostnameDir(t *testing.T) {
t.Parallel()
testCtx := setupTestHandlers(t)
createdApp := createTestApp(t, testCtx, "log-old-hostname-app")
deployment := models.NewDeployment(testCtx.database)
deployment.AppID = createdApp.ID
deployment.Status = models.DeploymentStatusSuccess
require.NoError(t, deployment.Save(context.Background()))
logDir := testCtx.deploySvc.GetLogDir()
newPath := testCtx.deploySvc.GetLogFilePath(createdApp, deployment)
relPath, relErr := filepath.Rel(logDir, newPath)
require.NoError(t, relErr)
oldPath := filepath.Join(logDir, "old-container-hostname", relPath)
require.NoError(t, os.MkdirAll(filepath.Dir(oldPath), 0o750))
require.NoError(t, os.WriteFile(oldPath, []byte("old deploy log contents"), 0o600))
recorder := doLogDownload(t, testCtx, createdApp.ID, deployment.ID)
assert.Equal(t, http.StatusOK, recorder.Code)
assert.Contains(t, recorder.Body.String(), "old deploy log contents")
}
// TestHandleDeploymentLogDownloadRejectsPathTraversal verifies the
// os.Root containment guard. A traversal-shaped app name drives the
// resolved log path out of the deploy log directory onto a sentinel