diff --git a/core/src/main/java/hudson/model/WorkspaceCleanupThread.java b/core/src/main/java/hudson/model/WorkspaceCleanupThread.java index 9e83e143e5a3..a7a606b4767f 100644 --- a/core/src/main/java/hudson/model/WorkspaceCleanupThread.java +++ b/core/src/main/java/hudson/model/WorkspaceCleanupThread.java @@ -27,6 +27,7 @@ import hudson.ExtensionList; import hudson.FilePath; import hudson.Util; +import hudson.slaves.WorkspaceList; import java.io.IOException; import java.util.ArrayList; import java.util.Date; @@ -89,6 +90,7 @@ public static void invoke() { listener.getLogger().println("Deleting " + ws + " on " + node.getDisplayName()); try { ws.deleteRecursive(); + WorkspaceList.tempDir(ws).deleteRecursive(); } catch (IOException x) { x.printStackTrace(listener.error("Failed to delete " + ws + " on " + node.getDisplayName())); } catch (InterruptedException x) { diff --git a/core/src/main/java/hudson/slaves/WorkspaceList.java b/core/src/main/java/hudson/slaves/WorkspaceList.java index a344b45aacb8..9b415998929f 100644 --- a/core/src/main/java/hudson/slaves/WorkspaceList.java +++ b/core/src/main/java/hudson/slaves/WorkspaceList.java @@ -26,6 +26,7 @@ import hudson.FilePath; import hudson.Functions; import hudson.model.Computer; +import hudson.model.DirectoryBrowserSupport; import java.io.Closeable; import java.util.Date; @@ -283,6 +284,25 @@ public void release() { }; } + /** + * Locates a conventional temporary directory to be associated with a workspace. + *

This directory is suitable for temporary files to be deleted later in the course of a build, + * or caches and local repositories which should persist across builds done in the same workspace. + * (If multiple workspaces are present for a single job built concurrently, via {@link #allocate(FilePath)}, each will get its own temporary directory.) + *

It may also be used for security-sensitive files which {@link DirectoryBrowserSupport} ought not serve, + * acknowledging that these will be readable by builds of other jobs done on the same node. + *

Each plugin using this directory is responsible for specifying sufficiently unique subdirectory/file names. + * {@link FilePath#createTempFile} may be used for this purpose if desired. + *

The resulting directory may not exist; you may call {@link FilePath#mkdirs} on it if you need it to. + * It may be deleted alongside the workspace itself during cleanup actions. + * @param ws a directory such as a build workspace + * @return a sibling directory, for example {@code …/something@tmp} for {@code …/something} + * @since 1.652 + */ + public static FilePath tempDir(FilePath ws) { + return ws.sibling(ws.getName() + COMBINATOR + "tmp"); + } + private static final Logger LOGGER = Logger.getLogger(WorkspaceList.class.getName()); /** diff --git a/test/src/test/java/hudson/model/WorkspaceCleanupThreadTest.java b/test/src/test/java/hudson/model/WorkspaceCleanupThreadTest.java index 28d7dd04e188..097127617936 100644 --- a/test/src/test/java/hudson/model/WorkspaceCleanupThreadTest.java +++ b/test/src/test/java/hudson/model/WorkspaceCleanupThreadTest.java @@ -28,6 +28,7 @@ import hudson.remoting.VirtualChannel; import hudson.scm.NullSCM; import hudson.slaves.DumbSlave; +import hudson.slaves.WorkspaceList; import hudson.util.StreamTaskListener; import java.io.File; @@ -180,6 +181,19 @@ public class WorkspaceCleanupThreadTest { assertFalse(ws.exists()); } + @Issue("JENKINS-27152") + @Test + public void deleteTemporaryDirectory() throws Exception { + FreeStyleProject p = r.createFreeStyleProject(); + FilePath ws = createOldWorkspaceOn(r.jenkins, p); + FilePath tmp = WorkspaceList.tempDir(ws); + tmp.child("stuff").write("content", null); + createOldWorkspaceOn(r.createOnlineSlave(), p); + performCleanup(); + assertFalse(ws.exists()); + assertFalse("temporary directory should be cleaned up as well", tmp.exists()); + } + private FilePath createOldWorkspaceOn(Node slave, FreeStyleProject p) throws Exception { p.setAssignedNode(slave); FreeStyleBuild b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0));