diff --git a/pom.xml b/pom.xml index e15a10ac..43458dbf 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,12 @@ import pom + + + org.jenkins-ci.plugins.workflow + workflow-cps + 3637.v63b_c17e0ed5b_ + diff --git a/src/main/java/org/jenkinsci/plugins/workflow/cps/global/UserDefinedGlobalVariable.java b/src/main/java/org/jenkinsci/plugins/workflow/cps/global/UserDefinedGlobalVariable.java index 7ec37c28..35b1e978 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/cps/global/UserDefinedGlobalVariable.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/cps/global/UserDefinedGlobalVariable.java @@ -1,20 +1,21 @@ package org.jenkinsci.plugins.workflow.cps.global; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import groovy.lang.Binding; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; import java.nio.charset.StandardCharsets; -import org.apache.commons.io.FileUtils; +import jenkins.model.Jenkins; +import org.apache.commons.io.IOUtils; import org.codehaus.groovy.control.MultipleCompilationErrorsException; import org.jenkinsci.plugins.workflow.cps.CpsCompilationErrorsException; import org.jenkinsci.plugins.workflow.cps.CpsScript; import org.jenkinsci.plugins.workflow.cps.CpsThread; import org.jenkinsci.plugins.workflow.cps.GlobalVariable; -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.io.File; -import java.io.IOException; -import jenkins.model.Jenkins; - /** * Global variable backed by user-supplied script. * @@ -22,10 +23,18 @@ */ // not @Extension because these are instantiated programmatically public class UserDefinedGlobalVariable extends GlobalVariable { - private final File help; + private final URI help; private final String name; + /** + * @deprecated use {@link #UserDefinedGlobalVariable(String, URI)} + */ + @Deprecated public UserDefinedGlobalVariable(String name, File help) { + this(name, help.toURI()); + } + + public UserDefinedGlobalVariable(String name, URI help) { this.name = name; this.help = help; } @@ -69,12 +78,14 @@ public Object getValue(@NonNull CpsScript script) throws Exception { * Loads help from user-defined file, if available. */ public @CheckForNull String getHelpHtml() throws IOException { - if (!help.exists()) return null; - - return Jenkins.get().getMarkupFormatter().translate( - FileUtils.readFileToString(help, StandardCharsets.UTF_8). - // Util.escape translates \n but not \r, and we do not know what platform the library will be checked out on: - replace("\r\n", "\n")); + try { + return Jenkins.get().getMarkupFormatter().translate( + IOUtils.toString(help, StandardCharsets.UTF_8). + // Util.escape translates \n but not \r, and we do not know what platform the library will be checked out on: + replace("\r\n", "\n")); + } catch (FileNotFoundException x) { + return null; + } } @Override diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryAdder.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryAdder.java index 9ff64777..6a3c0b85 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryAdder.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryAdder.java @@ -24,7 +24,6 @@ package org.jenkinsci.plugins.workflow.libs; -import hudson.AbortException; import hudson.Extension; import hudson.ExtensionList; import hudson.FilePath; @@ -50,6 +49,13 @@ import java.util.logging.Logger; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.OutputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution; @@ -92,16 +98,22 @@ libraryChangelogs.put(parsed[0], changelogs.get(library)); librariesUnparsed.put(parsed[0], library); } + TaskListener listener = execution.getOwner().getListener(); List additions = new ArrayList<>(); LibrariesAction action = build.getAction(LibrariesAction.class); if (action != null) { // Resuming a build, so just look up what we loaded before. for (LibraryRecord record : action.getLibraries()) { - FilePath libDir = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName()); - for (String root : new String[] {"src", "vars"}) { - FilePath dir = libDir.child(root); - if (dir.isDirectory()) { - additions.add(new Addition(dir.toURI().toURL(), record.trusted)); + FilePath libJar = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName() + ".jar"); + if (libJar.exists()) { + additions.add(new Addition(libJar.toURI().toURL(), record.trusted)); + } else { + FilePath libDir = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName()); + if (libDir.isDirectory()) { + listener.getLogger().println("Migrating " + libDir + " to " + libJar); + LibraryRetriever.dir2Jar(record.getName(), libDir, libJar, listener); + libDir.deleteRecursive(); + additions.add(new Addition(libJar.toURI().toURL(), record.trusted)); } } String unparsed = librariesUnparsed.get(record.name); @@ -114,7 +126,6 @@ // Now we will see which libraries we want to load for this job. Map librariesAdded = new LinkedHashMap<>(); Map retrievers = new HashMap<>(); - TaskListener listener = execution.getOwner().getListener(); for (LibraryResolver kind : ExtensionList.lookup(LibraryResolver.class)) { boolean kindTrusted = kind.isTrusted(); for (LibraryConfiguration cfg : kind.forJob(build.getParent(), libraryVersions)) { @@ -147,9 +158,7 @@ // Now actually try to retrieve the libraries. for (LibraryRecord record : librariesAdded.values()) { listener.getLogger().println("Loading library " + record.name + "@" + record.version); - for (URL u : retrieve(record, retrievers.get(record.name), listener, build, execution)) { - additions.add(new Addition(u, record.trusted)); - } + additions.add(new Addition(retrieve(record, retrievers.get(record.name), listener, build, execution), record.trusted)); } return additions; } @@ -169,14 +178,14 @@ private enum CacheStatus { EXPIRED; } - private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration cachingConfiguration, @NonNull final FilePath versionCacheDir) + private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration cachingConfiguration, @NonNull final FilePath versionCacheJar) throws IOException, InterruptedException { if (cachingConfiguration.isRefreshEnabled()) { final long cachingMilliseconds = cachingConfiguration.getRefreshTimeMilliseconds(); - if(versionCacheDir.exists()) { - if ((versionCacheDir.lastModified() + cachingMilliseconds) > System.currentTimeMillis()) { + if(versionCacheJar.exists()) { + if ((versionCacheJar.lastModified() + cachingMilliseconds) > System.currentTimeMillis()) { return CacheStatus.VALID; } else { return CacheStatus.EXPIRED; @@ -185,7 +194,7 @@ private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration c return CacheStatus.DOES_NOT_EXIST; } } else { - if (versionCacheDir.exists()) { + if (versionCacheJar.exists()) { return CacheStatus.VALID; } else { return CacheStatus.DOES_NOT_EXIST; @@ -194,16 +203,16 @@ private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration c } /** Retrieve library files. */ - static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriever retriever, @NonNull TaskListener listener, @NonNull Run run, @NonNull CpsFlowExecution execution) throws Exception { + static URL retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriever retriever, @NonNull TaskListener listener, @NonNull Run run, @NonNull CpsFlowExecution execution) throws Exception { String name = record.name; String version = record.version; boolean changelog = record.changelog; LibraryCachingConfiguration cachingConfiguration = record.cachingConfiguration; - FilePath libDir = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName()); + FilePath libJar = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName() + ".jar"); Boolean shouldCache = cachingConfiguration != null; - final FilePath versionCacheDir = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), record.getDirectoryName()); + final FilePath versionCacheJar = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), record.getDirectoryName() + ".jar"); ReentrantReadWriteLock retrieveLock = getReadWriteLockFor(record.getDirectoryName()); - final FilePath lastReadFile = new FilePath(versionCacheDir, LibraryCachingConfiguration.LAST_READ_FILE); + final FilePath lastReadFile = versionCacheJar.sibling(record.getDirectoryName() + "." + LibraryCachingConfiguration.LAST_READ_FILE); if(shouldCache && cachingConfiguration.isExcluded(version)) { listener.getLogger().println("Library " + name + "@" + version + " is excluded from caching."); @@ -213,13 +222,13 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev if(shouldCache) { retrieveLock.readLock().lockInterruptibly(); try { - CacheStatus cacheStatus = getCacheStatus(cachingConfiguration, versionCacheDir); + CacheStatus cacheStatus = getCacheStatus(cachingConfiguration, versionCacheJar); if (cacheStatus == CacheStatus.DOES_NOT_EXIST || cacheStatus == CacheStatus.EXPIRED) { retrieveLock.readLock().unlock(); retrieveLock.writeLock().lockInterruptibly(); try { boolean retrieve = false; - switch (getCacheStatus(cachingConfiguration, versionCacheDir)) { + switch (getCacheStatus(cachingConfiguration, versionCacheJar)) { case VALID: listener.getLogger().println("Library " + name + "@" + version + " is cached. Copying from home."); break; @@ -229,9 +238,8 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev case EXPIRED: long cachingMinutes = cachingConfiguration.getRefreshTimeMinutes(); listener.getLogger().println("Library " + name + "@" + version + " is due for a refresh after " + cachingMinutes + " minutes, clearing."); - if (versionCacheDir.exists()) { - versionCacheDir.deleteRecursive(); - versionCacheDir.withSuffix("-name.txt").delete(); + if (versionCacheJar.exists()) { + versionCacheJar.delete(); } retrieve = true; break; @@ -239,8 +247,7 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev if (retrieve) { listener.getLogger().println("Caching library " + name + "@" + version); - versionCacheDir.mkdirs(); - retriever.retrieve(name, version, changelog, versionCacheDir, run, listener); + retriever.retrieveJar(name, version, changelog, versionCacheJar, run, listener); } retrieveLock.readLock().lock(); } finally { @@ -251,50 +258,52 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev } lastReadFile.touch(System.currentTimeMillis()); - versionCacheDir.withSuffix("-name.txt").write(name, "UTF-8"); - versionCacheDir.copyRecursiveTo(libDir); + versionCacheJar.copyTo(libJar); } finally { retrieveLock.readLock().unlock(); } } else { - retriever.retrieve(name, version, changelog, libDir, run, listener); + retriever.retrieveJar(name, version, changelog, libJar, run, listener); } - // Write the user-provided name to a file as a debugging aid. - libDir.withSuffix("-name.txt").write(name, "UTF-8"); // Replace any classes requested for replay: if (!record.trusted) { - for (String clazz : ReplayAction.replacementsIn(execution)) { - for (String root : new String[] {"src", "vars"}) { - String rel = root + "/" + clazz.replace('.', '/') + ".groovy"; - FilePath f = libDir.child(rel); - if (f.exists()) { - String replacement = ReplayAction.replace(execution, clazz); - if (replacement != null) { - listener.getLogger().println("Replacing contents of " + rel); - f.write(replacement, null); // TODO as below, unsure of encoding used by Groovy compiler + Set clazzes = ReplayAction.replacementsIn(execution); + if (!clazzes.isEmpty()) { + FilePath tmp = libJar.withSuffix(".tmp"); + try { + libJar.unzip(tmp); + for (String clazz : clazzes) { + String rel = clazz.replace('.', '/') + ".groovy"; + FilePath f = tmp.child(rel); + if (f.exists()) { + String replacement = ReplayAction.replace(execution, clazz); + if (replacement != null) { + listener.getLogger().println("Replacing contents of " + rel); + f.write(replacement, null); // TODO as below, unsure of encoding used by Groovy compiler + } } } + libJar.delete(); + try (OutputStream os = libJar.write()) { + tmp.zip(os, "**"); + } + } finally { + tmp.deleteRecursive(); } } } - List urls = new ArrayList<>(); - FilePath srcDir = libDir.child("src"); - if (srcDir.isDirectory()) { - urls.add(srcDir.toURI().toURL()); - } - FilePath varsDir = libDir.child("vars"); - if (varsDir.isDirectory()) { - urls.add(varsDir.toURI().toURL()); - for (FilePath var : varsDir.list("*.groovy")) { - record.variables.add(var.getBaseName()); - } - } - if (urls.isEmpty()) { - throw new AbortException("Library " + name + " expected to contain at least one of src or vars directories"); + try (JarFile jf = new JarFile(libJar.getRemote())) { + jf.stream().forEach(entry -> { + Matcher m = ROOT_GROOVY_SOURCE.matcher(entry.getName()); + if (m.matches()) { + record.variables.add(m.group(1)); + } + }); } - return urls; + return libJar.toURI().toURL(); } + private static final Pattern ROOT_GROOVY_SOURCE = Pattern.compile("([^/]+)[.]groovy"); /** * Loads resources for {@link ResourceStep}. @@ -311,12 +320,18 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev if (action != null) { FilePath libs = new FilePath(run.getRootDir()).child("libs"); for (LibraryRecord library : action.getLibraries()) { - FilePath libResources = libs.child(library.getDirectoryName() + "/resources/"); - FilePath f = libResources.child(name); - if (!new File(f.getRemote()).getCanonicalFile().toPath().startsWith(new File(libResources.getRemote()).getCanonicalPath())) { - throw new AbortException(name + " references a file that is not contained within the library: " + library.name); - } else if (f.exists()) { - resources.put(library.name, readResource(f, encoding)); + FilePath libJar = libs.child(library.getDirectoryName() + ".jar"); + try (JarFile jf = new JarFile(libJar.getRemote())) { + JarEntry je = jf.getJarEntry("resources/" + name); + if (je != null) { + try (InputStream in = jf.getInputStream(je)) { + if ("Base64".equals(encoding)) { + resources.put(library.name, Base64.getEncoder().encodeToString(IOUtils.toByteArray(in))); + } else { + resources.put(library.name, IOUtils.toString(in, encoding)); // The platform default is used if encoding is null. + } + } + } } } } @@ -324,16 +339,6 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev return resources; } - private static String readResource(FilePath file, @CheckForNull String encoding) throws IOException, InterruptedException { - try (InputStream in = file.read()) { - if ("Base64".equals(encoding)) { - return Base64.getEncoder().encodeToString(IOUtils.toByteArray(in)); - } else { - return IOUtils.toString(in, encoding); // The platform default is used if encoding is null. - } - } - } - @Extension public static class GlobalVars extends GlobalVariableSet { @Override public Collection forRun(Run run) { @@ -347,7 +352,7 @@ private static String readResource(FilePath file, @CheckForNull String encoding) List vars = new ArrayList<>(); for (LibraryRecord library : action.getLibraries()) { for (String variable : library.variables) { - vars.add(new UserDefinedGlobalVariable(variable, new File(run.getRootDir(), "libs/" + library.getDirectoryName() + "/vars/" + variable + ".txt"))); + vars.add(new UserDefinedGlobalVariable(variable, URI.create("jar:" + new File(run.getRootDir(), "libs/" + library.getDirectoryName() + ".jar").toURI() + "!/" + variable + ".txt"))); } } return vars; @@ -372,14 +377,19 @@ private static String readResource(FilePath file, @CheckForNull String encoding) if (library.trusted) { continue; // TODO JENKINS-41157 allow replay of trusted libraries if you have ADMINISTER } - for (String rootName : new String[] {"src", "vars"}) { - FilePath root = libs.child(library.getDirectoryName() + "/" + rootName); - if (!root.isDirectory()) { - continue; - } - for (FilePath groovy : root.list("**/*.groovy")) { - String clazz = className(groovy.getRemote(), root.getRemote()); - scripts.put(clazz, groovy.readToString()); // TODO no idea what encoding the Groovy compiler uses + FilePath jar = libs.child(library.getDirectoryName() + ".jar"); + if (!jar.exists()) { + continue; + } + try (JarFile jf = new JarFile(jar.getRemote())) { + for (JarEntry je : (Iterable) jf.stream()::iterator) { + if (je.getName().endsWith(".groovy")) { + String text; + try (InputStream is = jf.getInputStream(je)) { + text = IOUtils.toString(is, StandardCharsets.UTF_8); // TODO no idea what encoding the Groovy compiler uses + } + scripts.put(je.getName().replaceFirst("[.]groovy$", "").replace('/', '.'), text); + } } } } @@ -391,10 +401,6 @@ private static String readResource(FilePath file, @CheckForNull String encoding) return scripts; } - static String className(String groovy, String root) { - return groovy.replaceFirst("^" + Pattern.quote(root) + "[/\\\\](.+)[.]groovy", "$1").replace('/', '.').replace('\\', '.'); - } - } @Extension public static class Copier extends FlowCopier.ByRun { diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanup.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanup.java index 591dcc0a..8946f394 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanup.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanup.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; +import jenkins.model.Jenkins; import jenkins.util.SystemProperties; @@ -27,41 +28,37 @@ public LibraryCachingCleanup() { @Override protected void execute(TaskListener listener) throws IOException, InterruptedException { FilePath globalCacheDir = LibraryCachingConfiguration.getGlobalLibrariesCacheDir(); - for (FilePath library : globalCacheDir.list()) { - if (!removeIfExpiredCacheDirectory(library)) { - // Prior to the SECURITY-2586 fix, library caches had a two-level directory structure. - // These caches will never be used again, so we delete any that we find. - for (FilePath version: library.list()) { - if (version.child(LibraryCachingConfiguration.LAST_READ_FILE).exists()) { - library.deleteRecursive(); - break; - } - } - } + for (FilePath libJar : globalCacheDir.list("*.jar")) { + removeIfExpiredCacheJar(libJar, listener); } + // Old cache directory; format has changed, so just delete it: + Jenkins.get().getRootPath().child("global-libraries-cache").deleteRecursive(); } /** - * Delete the specified cache directory if it is outdated. - * @return true if specified directory is a cache directory, regardless of whether it was outdated. Used to detect - * whether the cache was created before or after the fix for SECURITY-2586. + * Delete the specified cache JAR if it is outdated. */ - private boolean removeIfExpiredCacheDirectory(FilePath library) throws IOException, InterruptedException { - final FilePath lastReadFile = new FilePath(library, LibraryCachingConfiguration.LAST_READ_FILE); - if (lastReadFile.exists()) { - ReentrantReadWriteLock retrieveLock = LibraryAdder.getReadWriteLockFor(library.getName()); + private void removeIfExpiredCacheJar(FilePath libJar, TaskListener listener) throws IOException, InterruptedException { + final FilePath lastReadFile = libJar.sibling(libJar.getBaseName() + "." + LibraryCachingConfiguration.LAST_READ_FILE); + if (lastReadFile != null && lastReadFile.exists()) { + ReentrantReadWriteLock retrieveLock = LibraryAdder.getReadWriteLockFor(libJar.getBaseName()); retrieveLock.writeLock().lockInterruptibly(); try { if (System.currentTimeMillis() - lastReadFile.lastModified() > TimeUnit.DAYS.toMillis(EXPIRE_AFTER_READ_DAYS)) { - - library.deleteRecursive(); - library.withSuffix("-name.txt").delete(); + listener.getLogger().println("Deleting " + libJar); + try { + libJar.delete(); + } finally { + lastReadFile.delete(); + } + } else { + listener.getLogger().println(lastReadFile + " is sufficiently recent"); } } finally { retrieveLock.writeLock().unlock(); } - return true; + } else { + listener.getLogger().println("No such file " + lastReadFile); } - return false; } } diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration.java index afc3dfad..8cf77fa8 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration.java @@ -10,17 +10,15 @@ import org.kohsuke.stapler.QueryParameter; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.jar.JarFile; import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; public final class LibraryCachingConfiguration extends AbstractDescribableImpl { @@ -31,7 +29,7 @@ public final class LibraryCachingConfiguration extends AbstractDescribableImpl implements ExtensionPoint { + /** + * JAR manifest attribute giving original library name. + */ + static final String ATTR_LIBRARY_NAME = "Jenkins-Library-Name"; + + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Non-final for write access via the Script Console") + public static boolean INCLUDE_SRC_TEST_IN_LIBRARIES = Boolean.getBoolean(SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES"); + + /** + * Obtains library sources. + * @param name the {@link LibraryConfiguration#getName} + * @param version the version of the library, such as from {@link LibraryConfiguration#getDefaultVersion} or an override + * @param changelog whether to include changesets in the library in jobs using it from {@link LibraryConfiguration#getIncludeInChangesets} + * @param target a JAR file in which to stash sources; should contain {@code **}{@code /*.groovy} (sources at top level will be considered vars), and optionally also {@code resources/} + * @param run a build which will use the library + * @param listener a way to report progress + * @throws Exception if there is any problem (use {@link AbortException} for user errors) + */ + public void retrieveJar(@NonNull String name, @NonNull String version, boolean changelog, @NonNull FilePath target, @NonNull Run run, @NonNull TaskListener listener) throws Exception { + if (Util.isOverridden(LibraryRetriever.class, getClass(), "retrieve", String.class, String.class, boolean.class, FilePath.class, Run.class, TaskListener.class)) { + FilePath tmp = target.sibling(target.getBaseName() + "-checkout"); + if (tmp == null) { + throw new IOException(); + } + try { + retrieve(name, version, changelog, tmp, run, listener); + dir2Jar(name, tmp, target, listener); + } finally { + tmp.deleteRecursive(); + FilePath tmp2 = WorkspaceList.tempDir(tmp); + if (tmp2 != null) { + tmp2.deleteRecursive(); + } + } + } else { + throw new AbstractMethodError("Implement retrieveJar"); + } + } + + /** + * Translates a historical directory with {@code src/} and/or {@code vars/} and/or {@code resources/} subdirectories + * into a JAR file with Groovy in classpath orientation and {@code resources/} as a ZIP folder. + */ + static void dir2Jar(@NonNull String name, @NonNull FilePath dir, @NonNull FilePath jar, @NonNull TaskListener listener) throws IOException, InterruptedException { + lookForBadSymlinks(dir, dir); + FilePath mf = jar.withSuffix(".mf"); + try { + try (OutputStream os = mf.write()) { + Manifest m = new Manifest(); + m.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + // Informational debugging aid, since the hex JAR basename will be meaningless: + m.getMainAttributes().putValue(ATTR_LIBRARY_NAME, name); + m.write(os); + } + try (OutputStream os = jar.write()) { + dir.archive(ArchiverFactory.ZIP, os, new DirScanner() { + @Override public void scan(File dir, FileVisitor visitor) throws IOException { + scanSingle(new File(mf.getRemote()), JarFile.MANIFEST_NAME, visitor); + String excludes; + if (!INCLUDE_SRC_TEST_IN_LIBRARIES && new File(dir, "src/test").isDirectory()) { + excludes = "test/"; + listener.getLogger().println("Excluding src/test/ so that library test code cannot be accessed by Pipelines."); + listener.getLogger().println("To remove this log message, move the test code outside of src/. To restore the previous behavior that allowed access to files in src/test/, pass -D" + SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES=true to the java command used to start Jenkins."); + } else { + excludes = null; + } + AtomicBoolean found = new AtomicBoolean(); + FileVisitor verifyingVisitor = visitor.with(pathname -> { + if (pathname.getName().endsWith(".groovy")) { + found.set(true); + } + return true; + }); + new DirScanner.Glob("**/*.groovy", excludes).scan(new File(dir, "src"), verifyingVisitor); + new DirScanner.Glob("*.groovy,*.txt", null).scan(new File(dir, "vars"), verifyingVisitor); + if (!found.get()) { + throw new AbortException("Library " + name + " expected to contain at least one of src or vars directories"); + } + new DirScanner.Glob("resources/", null).scan(dir, visitor); + } + }); + } + } finally { + mf.delete(); + } + } + + private static void lookForBadSymlinks(FilePath root, FilePath dir) throws IOException, InterruptedException { + for (FilePath child : dir.list()) { + if (child.isDirectory()) { + lookForBadSymlinks(root, child); + } else { + String link = child.readLink(); + if (link != null) { + Path target = Path.of(dir.getRemote(), link).toRealPath(); + if (!target.startsWith(Path.of(root.getRemote()))) { + throw new SecurityException(child + " → " + target + " is not inside " + root); + } + } + } + } + } + /** * Obtains library sources. * @param name the {@link LibraryConfiguration#getName} @@ -51,7 +167,14 @@ public abstract class LibraryRetriever extends AbstractDescribableImpl run, @NonNull TaskListener listener) throws Exception; + @Deprecated + public void retrieve(@NonNull String name, @NonNull String version, boolean changelog, @NonNull FilePath target, @NonNull Run run, @NonNull TaskListener listener) throws Exception { + if (Util.isOverridden(LibraryRetriever.class, getClass(), "retrieve", String.class, String.class, FilePath.class, Run.class, TaskListener.class)) { + retrieve(name, version, target, run, listener); + } else { + throw new AbstractMethodError("Implement retrieveJar"); + } + } /** * Obtains library sources. @@ -62,8 +185,10 @@ public abstract class LibraryRetriever extends AbstractDescribableImpl run, @NonNull TaskListener listener) throws Exception; + @Deprecated + public void retrieve(@NonNull String name, @NonNull String version, @NonNull FilePath target, @NonNull Run run, @NonNull TaskListener listener) throws Exception { + throw new AbstractMethodError("Implement retrieveJar"); + } @Deprecated public FormValidation validateVersion(@NonNull String name, @NonNull String version) { diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryStep.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryStep.java index 69f709aa..19a2e6e7 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryStep.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryStep.java @@ -39,27 +39,24 @@ import hudson.model.TaskListener; import hudson.scm.SCM; import hudson.security.AccessControlled; -import java.io.File; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.Paths; -import java.security.CodeSource; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.TreeSet; -import java.util.logging.Level; import java.util.logging.Logger; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import groovy.lang.MissingPropertyException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.inject.Inject; import jenkins.model.Jenkins; import jenkins.scm.impl.SingleSCMSource; @@ -229,9 +226,7 @@ else if (retriever instanceof SCMRetriever) { listener.getLogger().println("Loading library " + record.name + "@" + record.version); CpsFlowExecution exec = (CpsFlowExecution) getContext().get(FlowExecution.class); GroovyClassLoader loader = (trusted ? exec.getTrustedShell() : exec.getShell()).getClassLoader(); - for (URL u : LibraryAdder.retrieve(record, retriever, listener, run, (CpsFlowExecution) getContext().get(FlowExecution.class))) { - loader.addURL(u); - } + loader.addURL(LibraryAdder.retrieve(record, retriever, listener, run, (CpsFlowExecution) getContext().get(FlowExecution.class))); run.save(); // persist changes to LibrariesAction.libraries*.variables return new LoadedClasses(name, record.getDirectoryName(), trusted, changelog, run); } @@ -253,20 +248,40 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S private final @NonNull String prefix; /** {@link Class#getName} minus package prefix */ private final @CheckForNull String clazz; - /** {@code file:/…/libs/NAME/src/} */ - private final @NonNull String srcUrl; + /** {@code file:/…/libs/NAME/src/}, migrated to {@link #directoryName} */ + @Deprecated + private @CheckForNull String srcUrl; + /** {@link LibraryRecord#getDirectoryName}, or null if resuming a pre-dir2Jar build */ + private final @Nullable String directoryName; LoadedClasses(String library, String libraryDirectoryName, boolean trusted, Boolean changelog, Run run) { - this(library, trusted, changelog, "", null, /* cf. LibraryAdder.retrieve */ new File(run.getRootDir(), "libs/" + libraryDirectoryName + "/src").toURI().toString()); + this(library, trusted, changelog, "", null, libraryDirectoryName); } - LoadedClasses(String library, boolean trusted, Boolean changelog, String prefix, String clazz, String srcUrl) { + LoadedClasses(String library, boolean trusted, Boolean changelog, String prefix, String clazz, String directoryName) { this.library = library; this.trusted = trusted; this.changelog = changelog; this.prefix = prefix; this.clazz = clazz; - this.srcUrl = srcUrl; + this.directoryName = directoryName; + } + + private static final Pattern SRC_URL = Pattern.compile("file:/.+/([0-9a-f]{64})/src/"); + + private Object readResolve() throws IllegalAccessException { + if (srcUrl != null) { + Matcher m = SRC_URL.matcher(srcUrl); + if (!m.matches()) { + // Perhaps predating hash-based naming (ace0de3, Feb 2022): + throw new IllegalAccessException("Unexpected form of library source URL: " + srcUrl); + } + String inferredDirectoryName = m.group(1); + LOGGER.fine(() -> "deserializing to " + inferredDirectoryName); + return new LoadedClasses(library, trusted, changelog, prefix, clazz, inferredDirectoryName); + } else { + return this; + } } @Override public Object getProperty(String property) { @@ -290,12 +305,12 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S String fullClazz = clazz != null ? clazz + '$' + property : property; loadClass(prefix + fullClazz); // OK, class really exists, stash it and await methods - return new LoadedClasses(library, trusted, changelog, prefix, fullClazz, srcUrl); + return new LoadedClasses(library, trusted, changelog, prefix, fullClazz, directoryName); } else if (clazz != null) { throw new MissingPropertyException(property, loadClass(prefix + clazz)); } else { // Still selecting package components. - return new LoadedClasses(library, trusted, changelog, prefix + property + '.', null, srcUrl); + return new LoadedClasses(library, trusted, changelog, prefix + property + '.', null, directoryName); } } @@ -341,6 +356,8 @@ private static boolean isSandboxed() { // TODO putProperty for static field set + private static final Pattern JAR_URL = Pattern.compile("jar:file:/.+/([0-9a-f]{64})[.]jar!/.+"); + private Class loadClass(String name) { CpsFlowExecution exec = CpsThread.current().getExecution(); GroovyClassLoader loader = (trusted ? exec.getTrustedShell() : exec.getShell()).getClassLoader(); @@ -353,16 +370,19 @@ private Class loadClass(String name) { if (definingLoader != loader) { throw new IllegalAccessException("cannot access " + c + " via library handle: " + definingLoader + " is not " + loader); } - // Note that this goes through GroovyCodeSource.(File, String), which unlike (say) URLClassLoader set the “location” to the actual file, *not* the root. - CodeSource codeSource = c.getProtectionDomain().getCodeSource(); - if (codeSource == null) { - throw new IllegalAccessException(name + " had no defined code source"); + URL res = loader.getResource(name.replaceFirst("[$][^.]+$", "").replace('.', '/') + ".groovy"); + if (res == null) { + throw new IllegalAccessException("Unknown where " + name + " (" + c.getProtectionDomain().getCodeSource().getLocation() + ") was loaded from"); + } + Matcher m = JAR_URL.matcher(res.toString()); + if (!m.matches()) { + throw new IllegalAccessException("Unexpected URL " + res); } - String actual = canonicalize(codeSource.getLocation().toString()); - String srcUrlC = canonicalize(srcUrl); // do not do this in constructor: path might not actually exist - if (!actual.startsWith(srcUrlC)) { - throw new IllegalAccessException(name + " was defined in " + actual + " which was not inside " + srcUrlC); + String actual = m.group(1); + if (!actual.equals(directoryName)) { + throw new IllegalAccessException(name + " was defined in " + res + " rather than the expected " + directoryName); } + LOGGER.fine(() -> "loaded " + name + " from " + res + " ~ " + actual + " as expected"); if (!Modifier.isPublic(c.getModifiers())) { // unlikely since Groovy makes classes implicitly public throw new IllegalAccessException(c + " is not public"); } @@ -374,16 +394,6 @@ private Class loadClass(String name) { } } - private static String canonicalize(String uri) { - if (uri.startsWith("file:/")) { - try { - return Paths.get(new URI(uri)).toRealPath().toUri().toString(); - } catch (IOException | URISyntaxException x) { - LOGGER.log(Level.WARNING, "could not canonicalize " + uri, x); - } - } - return uri; - } } diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMBasedRetriever.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMBasedRetriever.java index d9f3a680..bf870409 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMBasedRetriever.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMBasedRetriever.java @@ -26,7 +26,6 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.AbortException; import hudson.Extension; import hudson.FilePath; @@ -48,7 +47,6 @@ import java.io.InterruptedIOException; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.logging.Level; @@ -70,9 +68,6 @@ public abstract class SCMBasedRetriever extends LibraryRetriever { private static final Logger LOGGER = Logger.getLogger(SCMBasedRetriever.class.getName()); - @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Non-final for write access via the Script Console") - public static boolean INCLUDE_SRC_TEST_IN_LIBRARIES = Boolean.getBoolean(SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES"); - /** * Matches ".." in positions where it would be treated as the parent directory. * @@ -126,55 +121,21 @@ protected final void doRetrieve(String name, boolean changelog, @NonNull SCM scm delegate.setChangelog(changelog); Node node = Jenkins.get(); if (clone) { - if (libraryPath == null) { - retrySCMOperation(listener, () -> { - delegate.checkout(run, target, listener, Jenkins.get().createLauncher(listener)); - WorkspaceList.tempDir(target).deleteRecursive(); - return null; - }); - } else { - FilePath root = target.child("root"); + FilePath tmp = target.sibling(target.getBaseName() + "-checkout"); + if (tmp == null) { + throw new IOException(); + } + try { retrySCMOperation(listener, () -> { - delegate.checkout(run, root, listener, Jenkins.get().createLauncher(listener)); - WorkspaceList.tempDir(root).deleteRecursive(); + delegate.checkout(run, tmp, listener, node.createLauncher(listener)); return null; }); - FilePath subdir = root.child(libraryPath); - if (!subdir.isDirectory()) { - throw new AbortException("Did not find " + libraryPath + " in checkout"); - } - for (String content : List.of("src", "vars", "resources")) { - FilePath contentDir = subdir.child(content); - if (contentDir.isDirectory()) { - LOGGER.fine(() -> "Moving " + content + " to top level in " + target); - contentDir.renameTo(target.child(content)); - } - } - // root itself will be deleted below - } - if (!INCLUDE_SRC_TEST_IN_LIBRARIES) { - FilePath srcTest = target.child("src/test"); - if (srcTest.isDirectory()) { - listener.getLogger().println("Excluding src/test/ from checkout of " + scm.getKey() + " so that library test code cannot be accessed by Pipelines."); - listener.getLogger().println("To remove this log message, move the test code outside of src/. To restore the previous behavior that allowed access to files in src/test/, pass -D" + SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES=true to the java command used to start Jenkins."); - srcTest.deleteRecursive(); - } - } - for (FilePath child : target.list()) { - String subdir = child.getName(); - switch (subdir) { - case "src": - // TODO delete everything that is not *.groovy - break; - case "vars": - // TODO delete everything that is not *.groovy or *.txt, incl. subdirs - break; - case "resources": - // OK, leave it all - break; - default: - child.deleteRecursive(); - LOGGER.fine(() -> "Deleted " + child); + LibraryRetriever.dir2Jar(name, libraryPath != null ? tmp.child(libraryPath) : tmp, target, listener); + } finally { + tmp.deleteRecursive(); + FilePath tmp2 = WorkspaceList.tempDir(tmp); + if (tmp2 != null) { + tmp2.deleteRecursive(); } } } else { // !clone @@ -200,17 +161,9 @@ protected final void doRetrieve(String name, boolean changelog, @NonNull SCM scm delegate.checkout(run, lease.path, listener, node.createLauncher(listener)); return null; }); - if (libraryPath == null) { - libraryPath = "."; - } - String excludes = INCLUDE_SRC_TEST_IN_LIBRARIES ? null : "src/test/"; - if (lease.path.child(libraryPath).child("src/test").exists()) { - listener.getLogger().println("Excluding src/test/ from checkout of " + scm.getKey() + " so that library test code cannot be accessed by Pipelines."); - listener.getLogger().println("To remove this log message, move the test code outside of src/. To restore the previous behavior that allowed access to files in src/test/, pass -D" + SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES=true to the java command used to start Jenkins."); - } // Cannot add WorkspaceActionImpl to private CpsFlowExecution.flowStartNodeActions; do we care? // Copy sources with relevant files from the checkout: - lease.path.child(libraryPath).copyRecursiveTo("src/**/*.groovy,vars/*.groovy,vars/*.txt,resources/", excludes, target); + LibraryRetriever.dir2Jar(name, libraryPath != null ? lease.path.child(libraryPath) : lease.path, target, listener); } } } diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMRetriever.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMRetriever.java index 2b01b819..8a9f1a19 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMRetriever.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMRetriever.java @@ -61,14 +61,10 @@ public SCM getScm() { return scm; } - @Override public void retrieve(String name, String version, boolean changelog, FilePath target, Run run, TaskListener listener) throws Exception { + @Override public void retrieveJar(String name, String version, boolean changelog, FilePath target, Run run, TaskListener listener) throws Exception { doRetrieve(name, changelog, scm, target, run, listener); } - @Override public void retrieve(String name, String version, FilePath target, Run run, TaskListener listener) throws Exception { - retrieve(name, version, true, target, run, listener); - } - @Override public FormValidation validateVersion(String name, String version, Item context) { if (!Items.XSTREAM2.toXML(scm).contains("${library." + name + ".version}")) { return FormValidation.warningWithMarkup("When using " + getDescriptor().getDisplayName() + ", you will need to include ${library." + Util.escape(name) + ".version} in the SCM configuration somewhere."); diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetriever.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetriever.java index 4ba9c5a6..fe0a10c3 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetriever.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetriever.java @@ -71,7 +71,7 @@ public SCMSource getScm() { return scm; } - @Override public void retrieve(String name, String version, boolean changelog, FilePath target, Run run, TaskListener listener) throws Exception { + @Override public void retrieveJar(String name, String version, boolean changelog, FilePath target, Run run, TaskListener listener) throws Exception { SCMRevision revision = retrySCMOperation(listener, () -> scm.fetch(version, listener, run.getParent())); if (revision == null) { throw new AbortException("No version " + version + " found for library " + name); @@ -79,10 +79,6 @@ public SCMSource getScm() { doRetrieve(name, changelog, scm.build(revision.getHead(), revision), target, run, listener); } - @Override public void retrieve(String name, String version, FilePath target, Run run, TaskListener listener) throws Exception { - retrieve(name, version, true, target, run, listener); - } - @Override public FormValidation validateVersion(String name, String version, Item context) { StringWriter w = new StringWriter(); try { diff --git a/src/test/java/org/jenkinsci/plugins/workflow/cps/global/GrapeTest.java b/src/test/java/org/jenkinsci/plugins/workflow/cps/global/GrapeTest.java index f84d8422..5fd678ef 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/cps/global/GrapeTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/cps/global/GrapeTest.java @@ -197,9 +197,6 @@ private static final class LocalRetriever extends LibraryRetriever { @Override public void retrieve(String name, String version, boolean changelog, FilePath target, Run run, TaskListener listener) throws Exception { new FilePath(lib).copyRecursiveTo(target); } - @Override public void retrieve(String name, String version, FilePath target, Run run, TaskListener listener) throws Exception { - retrieve(name, version, false, target, run, listener); - } } @Test public void outsideLibrary() throws Exception { diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest.java index 2cd590b8..495a9399 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest.java @@ -46,7 +46,6 @@ import jenkins.scm.impl.subversion.SubversionSCMSource; import jenkins.scm.impl.subversion.SubversionSampleRepoRule; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; import org.jenkinsci.plugins.workflow.cps.GlobalVariable; @@ -64,7 +63,6 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.TestExtension; -import org.jvnet.hudson.test.WithoutJenkins; import org.jvnet.hudson.test.recipes.LocalData; public class LibraryAdderTest { @@ -374,7 +372,7 @@ public class LibraryAdderTest { WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); p.setDefinition(new CpsFlowDefinition("@Library('lib@master') import test.Foo", true)); WorkflowRun b = r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0)); - r.assertLogContains("Excluding src/test/ from checkout", b); + r.assertLogContains("Excluding src/test/", b); r.assertLogContains("expected to contain at least one of src or vars directories", b); } @@ -475,6 +473,34 @@ public void correctLibraryDirectoryUsedWhenResumingOldBuild() throws Exception { r.assertLogContains("called Foo", b); } + @LocalData + @Test + public void correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild() throws Exception { + // LocalData captured as of 1091aea7fa252acae11389588addf603a505e195: + /* + sampleRepo.init(); + sampleRepo.write("vars/foo.groovy", "def call() { echo('called Foo') }"); + sampleRepo.git("add", "vars"); + sampleRepo.git("commit", "--message=init"); + GlobalLibraries.get().setLibraries(Collections.singletonList( + new LibraryConfiguration("lib", + new SCMSourceRetriever(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", true))))); + WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "@Library('lib@master') _\n" + + "sleep 180\n" + + "foo()", true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + r.waitForMessage("Sleeping for 3 min", b); + b.save(); + Thread.sleep(Long.MAX_VALUE); + */ + WorkflowJob p = r.jenkins.getItemByFullName("p", WorkflowJob.class); + WorkflowRun b = p.getBuildByNumber(1); + r.assertBuildStatus(Result.SUCCESS, r.waitForCompletion(b)); + r.assertLogContains("called Foo", b); + } + @Issue("JENKINS-66898") @Test public void parallelBuildsDontInterfereWithExpiredCache() throws Throwable { @@ -502,7 +528,7 @@ public void parallelBuildsDontInterfereWithExpiredCache() throws Throwable { WorkflowRun b1 = r.buildAndAssertSuccess(p1); LibrariesAction action = b1.getAction(LibrariesAction.class); LibraryRecord record = action.getLibraries().get(0); - FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName()); + FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName() + ".jar"); //Expire the cache long oldMillis = ZonedDateTime.now().minusMinutes(35).toInstant().toEpochMilli(); cache.touch(oldMillis); @@ -516,12 +542,4 @@ public void parallelBuildsDontInterfereWithExpiredCache() throws Throwable { // r.assertLogContains("Library library@master is cached. Copying from home.", f2.get()); } - @Issue("JENKINS-68544") - @WithoutJenkins - @Test public void className() { - assertThat(LibraryAdder.LoadedLibraries.className("/path/to/lib/src/some/pkg/Type.groovy", "/path/to/lib/src"), is("some.pkg.Type")); - assertThat(LibraryAdder.LoadedLibraries.className("C:\\path\\to\\lib\\src\\some\\pkg\\Type.groovy", "C:\\path\\to\\lib\\src"), is("some.pkg.Type")); - assertThat(LibraryAdder.LoadedLibraries.className("C:\\path\\to\\Extra\\lib\\src\\some\\pkg\\Type.groovy", "C:\\path\\to\\Extra\\lib\\src"), is("some.pkg.Type")); - } - } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanupTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanupTest.java index ff30c86e..a212588c 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanupTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanupTest.java @@ -27,7 +27,7 @@ import hudson.FilePath; import hudson.util.StreamTaskListener; import java.io.File; -import java.time.ZonedDateTime; +import java.util.concurrent.TimeUnit; import jenkins.plugins.git.GitSCMSource; import jenkins.plugins.git.GitSampleRepoRule; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; @@ -39,7 +39,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.not; -import static org.hamcrest.io.FileMatchers.anExistingDirectory; import static org.hamcrest.io.FileMatchers.anExistingFile; public class LibraryCachingCleanupTest { @@ -67,28 +66,16 @@ public void smokes() throws Throwable { WorkflowRun b = r.buildAndAssertSuccess(p); LibrariesAction action = b.getAction(LibrariesAction.class); LibraryRecord record = action.getLibraries().get(0); - FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName()); - assertThat(new File(cache.getRemote()), anExistingDirectory()); + FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName() + ".jar"); + assertThat(new File(cache.getRemote()), anExistingFile()); // Run LibraryCachingCleanup and show that cache is not deleted. ExtensionList.lookupSingleton(LibraryCachingCleanup.class).execute(StreamTaskListener.fromStderr()); - assertThat(new File(cache.getRemote()), anExistingDirectory()); - assertThat(new File(cache.withSuffix("-name.txt").getRemote()), anExistingFile()); + assertThat(new File(cache.getRemote()), anExistingFile()); // Run LibraryCachingCleanup after modifying LAST_READ_FILE to be an old date and and show that cache is deleted. - long oldMillis = ZonedDateTime.now().minusDays(LibraryCachingCleanup.EXPIRE_AFTER_READ_DAYS + 1).toInstant().toEpochMilli(); - cache.child(LibraryCachingConfiguration.LAST_READ_FILE).touch(oldMillis); + long oldMillis = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(LibraryCachingCleanup.EXPIRE_AFTER_READ_DAYS + 1); + cache.sibling(cache.getBaseName() + "." + LibraryCachingConfiguration.LAST_READ_FILE).touch(oldMillis); ExtensionList.lookupSingleton(LibraryCachingCleanup.class).execute(StreamTaskListener.fromStderr()); - assertThat(new File(cache.getRemote()), not(anExistingDirectory())); - assertThat(new File(cache.withSuffix("-name.txt").getRemote()), not(anExistingDirectory())); - } - - @Test - public void preSecurity2586() throws Throwable { - FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child("name").child("version"); - cache.mkdirs(); - cache.child(LibraryCachingConfiguration.LAST_READ_FILE).touch(System.currentTimeMillis()); - ExtensionList.lookupSingleton(LibraryCachingCleanup.class).execute(StreamTaskListener.fromStderr()); - assertThat(new File(cache.getRemote()), not(anExistingDirectory())); - assertThat(new File(cache.getParent().getRemote()), not(anExistingDirectory())); + assertThat(new File(cache.getRemote()), not(anExistingFile())); } } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfigurationTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfigurationTest.java index b2a0a55f..67c47068 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfigurationTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfigurationTest.java @@ -173,13 +173,11 @@ public void clearCache() throws Exception { WorkflowRun b = r.buildAndAssertSuccess(p); LibrariesAction action = b.getAction(LibrariesAction.class); LibraryRecord record = action.getLibraries().get(0); - FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName()); - assertThat(new File(cache.getRemote()), anExistingDirectory()); - assertThat(new File(cache.withSuffix("-name.txt").getRemote()), anExistingFile()); + FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName() + ".jar"); + assertThat(new File(cache.getRemote()), anExistingFile()); // Clear the cache. TODO: Would be more realistic to set up security and use WebClient. ExtensionList.lookupSingleton(LibraryCachingConfiguration.DescriptorImpl.class).doClearCache("library", false); - assertThat(new File(cache.getRemote()), not(anExistingDirectory())); - assertThat(new File(cache.withSuffix("-name.txt").getRemote()), not(anExistingFile())); + assertThat(new File(cache.getRemote()), not(anExistingFile())); } } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryRetrieverTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryRetrieverTest.java new file mode 100644 index 00000000..6958ec6b --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryRetrieverTest.java @@ -0,0 +1,116 @@ +/* + * The MIT License + * + * Copyright 2023 CloudBees, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.jenkinsci.plugins.workflow.libs; + +import hudson.FilePath; +import hudson.Functions; +import hudson.util.StreamTaskListener; +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.JarFile; +import org.apache.commons.io.IOUtils; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThrows; +import static org.junit.Assume.assumeFalse; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class LibraryRetrieverTest { + + @Rule public TemporaryFolder tmp = new TemporaryFolder(); + + @Test public void justVars() throws Exception { + assertDir2Jar(Set.of("vars/xxx.groovy", "vars/yyy.groovy"), Set.of("xxx.groovy", "yyy.groovy")); + } + + @Test public void justSrc() throws Exception { + assertDir2Jar(Set.of("src/p1/xxx.groovy", "src/p2/yyy.groovy"), Set.of("p1/xxx.groovy", "p2/yyy.groovy")); + } + + @Test public void theWorks() throws Exception { + assertDir2Jar(Set.of("src/p1/xxx.groovy", "vars/yyy.groovy", "resources/a.txt", "resources/b/c.txt"), Set.of("p1/xxx.groovy", "yyy.groovy", "resources/a.txt", "resources/b/c.txt")); + } + + @Test public void otherFiles() throws Exception { + assertDir2Jar(Set.of("vars/v.groovy", "vars/v.txt", "vars/README.md", "README.md", "docs/usage.png", "src/p1/C.groovy", "src/p1/README.md"), Set.of("v.groovy", "v.txt", "p1/C.groovy")); + } + + @Test public void safeSymlinks() throws Exception { + assumeFalse(Functions.isWindows()); + FilePath work = new FilePath(tmp.newFolder()); + FilePath dir = work.child("dir"); + dir.child("vars/x.groovy").write("content", null); + dir.child("resources/a.txt").write("content", null); + dir.child("resources/b.txt").symlinkTo("a.txt", StreamTaskListener.fromStderr()); + FilePath jar = work.child("x.jar"); + LibraryRetriever.dir2Jar("mylib", dir, jar, StreamTaskListener.fromStderr()); + try (JarFile jf = new JarFile(jar.getRemote())) { + assertThat(IOUtils.toString(jf.getInputStream(jf.getEntry("resources/a.txt")), StandardCharsets.UTF_8), is("content")); + assertThat(IOUtils.toString(jf.getInputStream(jf.getEntry("resources/b.txt")), StandardCharsets.UTF_8), is("content")); + } + } + + @Test public void unsafeSymlinks() throws Exception { + assumeFalse(Functions.isWindows()); + FilePath work = new FilePath(tmp.newFolder()); + FilePath dir = work.child("dir"); + dir.child("vars/x.groovy").write("content", null); + dir.child("resources").mkdirs(); + work.child("secret.txt").write("s3cr3t", null); + dir.child("resources/hack.txt").symlinkTo("../../secret.txt", StreamTaskListener.fromStderr()); + FilePath jar = work.child("x.jar"); + assertThrows(SecurityException.class, () -> LibraryRetriever.dir2Jar("mylib", dir, jar, StreamTaskListener.fromStderr())); + } + + private void assertDir2Jar(Set inputs, Set outputs) throws Exception { + FilePath work = new FilePath(tmp.newFolder()); + FilePath dir = work.child("dir"); + for (String input : inputs) { + dir.child(input).write("xxx", null); + } + FilePath jar = work.child("x.jar"); + var before = dir.list("**"); + LibraryRetriever.dir2Jar("mylib", dir, jar, StreamTaskListener.fromStderr()); + assertThat(dir.list("**"), arrayContainingInAnyOrder(before)); + Set actualOutputs = new TreeSet<>(); + try (JarFile jf = new JarFile(jar.getRemote())) { + assertThat(jf.getManifest().getMainAttributes().getValue(LibraryRetriever.ATTR_LIBRARY_NAME), is("mylib")); + jf.stream().forEach(e -> { + String name = e.getName(); + if (!name.endsWith("/") && !name.startsWith("META-INF/")) { + actualOutputs.add(name); + } + }); + } + assertThat(actualOutputs, is(outputs)); + assertThat(work.list(), containsInAnyOrder(dir, jar)); + } + +} diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryStepTest.java index 67f757c9..3eb12763 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryStepTest.java @@ -36,6 +36,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.logging.Level; import jenkins.plugins.git.GitSCMSource; import jenkins.plugins.git.GitSampleRepoRule; @@ -56,6 +57,8 @@ import org.jvnet.hudson.test.BuildWatcher; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.LoggerRule; +import org.jvnet.hudson.test.recipes.LocalData; @Issue("JENKINS-39450") public class LibraryStepTest { @@ -64,6 +67,7 @@ public class LibraryStepTest { @Rule public JenkinsRule r = new JenkinsRule(); @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); @Rule public GitSampleRepoRule sampleRepo2 = new GitSampleRepoRule(); + @Rule public LoggerRule logging = new LoggerRule().record(LibraryStep.class, Level.FINE); @Test public void configRoundtrip() throws Exception { StepConfigTester stepTester = new StepConfigTester(r); @@ -342,4 +346,27 @@ public class LibraryStepTest { r.assertLogContains("/lib/java", b); r.assertLogContains("/pipeline/java", b); } + + @LocalData + @Test public void classesWhenResumingPreDir2JarBuild() throws Exception { + // LocalData captured as of 1091aea7fa252acae11389588addf603a505e195: + /* + sampleRepo.init(); + sampleRepo.write("src/pkg/C.groovy", "package pkg; class C {static void m() {23}}"); + sampleRepo.git("add", "src"); + sampleRepo.git("commit", "--message=init"); + GlobalLibraries.get().setLibraries(Collections.singletonList(new LibraryConfiguration("stuff", new SCMSourceRetriever(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", true))))); + WorkflowJob p = r.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("def lib = library('stuff@master'); sleep 180; echo(/got ${lib.pkg.C.m()}/)", true)); + WorkflowRun b = p.scheduleBuild2(0).waitForStart(); + r.waitForMessage("Sleeping for 3 min", b); + b.save(); + Thread.sleep(Long.MAX_VALUE); + */ + WorkflowJob p = r.jenkins.getItemByFullName("p", WorkflowJob.class); + WorkflowRun b = p.getBuildByNumber(1); + r.assertBuildStatus(Result.SUCCESS, r.waitForCompletion(b)); + r.assertLogContains("got 23", b); + } + } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/ResourceStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/ResourceStepTest.java index c1faefca..769ccf1b 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/libs/ResourceStepTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/ResourceStepTest.java @@ -195,7 +195,7 @@ public class ResourceStepTest { Path resourcesDir = Paths.get(sampleRepo.getRoot().getPath(), "resources"); Files.createDirectories(resourcesDir); Path symlinkPath = Paths.get(resourcesDir.toString(), "master.key"); - Files.createSymbolicLink(symlinkPath, Paths.get("../../../../../../../secrets/master.key")); + Files.createSymbolicLink(symlinkPath, Paths.get("../../../../secrets/master.key")); sampleRepo.git("add", "src", "resources"); sampleRepo.git("commit", "--message=init"); @@ -204,7 +204,7 @@ public class ResourceStepTest { WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); p.setDefinition(new CpsFlowDefinition("@Library('symlink-stuff@master') import Stuff; echo(Stuff.contents(this))", true)); - r.assertLogContains("master.key references a file that is not contained within the library: symlink-stuff", r.buildAndAssertStatus(Result.FAILURE, p)); + r.assertLogContains("master.key is not inside", r.buildAndAssertStatus(Result.FAILURE, p)); } @Issue("SECURITY-2476") @@ -222,7 +222,7 @@ public class ResourceStepTest { WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); p.setDefinition(new CpsFlowDefinition("@Library('libres-stuff@master') import Stuff; echo(Stuff.contents(this))", true)); - r.assertLogContains("../../../../../../../secrets/master.key references a file that is not contained within the library: libres-stuff", r.buildAndAssertStatus(Result.FAILURE, p)); + r.assertLogContains("No such library resource ../../../../../../../secrets/master.key could be found.", r.buildAndAssertStatus(Result.FAILURE, p)); } @Test public void findResourcesAttemptsToLoadFromAllIncludedLibraries() throws Exception { @@ -271,9 +271,9 @@ public void clearCache(String name) throws Exception { public void modifyCacheTimestamp(String name, String version, long timestamp) throws Exception { String cacheDirName = LibraryRecord.directoryNameFor(name, version, String.valueOf(true), GlobalLibraries.ForJob.class.getName()); - FilePath cacheDir = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), cacheDirName); - if (cacheDir.exists()) { - cacheDir.touch(timestamp); + FilePath cacheJar = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), cacheDirName + ".jar"); + if (cacheJar.exists()) { + cacheJar.touch(timestamp); } } diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetrieverTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetrieverTest.java index 0b6a1583..1cd90fb6 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetrieverTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetrieverTest.java @@ -43,7 +43,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.logging.Level; import jenkins.plugins.git.GitSCMSource; import jenkins.plugins.git.GitSampleRepoRule; import jenkins.scm.api.SCMHead; @@ -81,15 +80,13 @@ import org.jvnet.hudson.test.WithoutJenkins; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.arrayContainingInAnyOrder; -import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.matchesPattern; import static org.hamcrest.Matchers.nullValue; import static org.jenkinsci.plugins.workflow.libs.SCMBasedRetriever.PROHIBITED_DOUBLE_DOT; import static org.junit.Assume.assumeFalse; import org.jvnet.hudson.test.FlagRule; -import org.jvnet.hudson.test.LoggerRule; public class SCMSourceRetrieverTest { @@ -97,8 +94,7 @@ public class SCMSourceRetrieverTest { @Rule public JenkinsRule r = new JenkinsRule(); @Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule(); @Rule public SubversionSampleRepoRule sampleRepoSvn = new SubversionSampleRepoRule(); - @Rule public FlagRule includeSrcTest = new FlagRule<>(() -> SCMBasedRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES, v -> SCMBasedRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = v); - @Rule public LoggerRule logging = new LoggerRule().record(SCMBasedRetriever.class, Level.FINE); + @Rule public FlagRule includeSrcTest = new FlagRule<>(() -> LibraryRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES, v -> LibraryRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = v); @Issue("JENKINS-40408") @Test public void lease() throws Exception { @@ -381,7 +377,6 @@ public static class BasicSCMSource extends SCMSource { sampleRepo.init(); sampleRepo.write("vars/myecho.groovy", "def call() {echo 'something special'}"); sampleRepo.write("README.md", "Summary"); - sampleRepo.git("rm", "file"); sampleRepo.git("add", "."); sampleRepo.git("commit", "--message=init"); GitSCMSource src = new GitSCMSource(sampleRepo.toString()); @@ -401,10 +396,8 @@ public static class BasicSCMSource extends SCMSource { r.assertLogContains("Using shallow clone with depth 1", b); r.assertLogContains("Avoid fetching tags", b); r.assertLogNotContains("+refs/heads/*:refs/remotes/origin/*", b); - File[] libDirs = new File(b.getRootDir(), "libs").listFiles(File::isDirectory); - assertThat(libDirs, arrayWithSize(1)); - String[] entries = libDirs[0].list(); - assertThat(entries, arrayContainingInAnyOrder("vars")); + // Fails to reproduce presence of *.jar.tmp@tmp; probably specific to use of GIT_ASKPASS: + assertThat(new File(b.getRootDir(), "libs").list(), arrayContaining(matchesPattern("[0-9a-f]{64}[.]jar"))); } @Test public void cloneModeLibraryPath() throws Exception { @@ -422,10 +415,6 @@ public static class BasicSCMSource extends SCMSource { p.setDefinition(new CpsFlowDefinition("@Library('root_sub_path@master') import myecho; myecho()", true)); WorkflowRun b = r.buildAndAssertSuccess(p); r.assertLogContains("something special", b); - File[] libDirs = new File(b.getRootDir(), "libs").listFiles(File::isDirectory); - assertThat(libDirs, arrayWithSize(1)); - String[] entries = libDirs[0].list(); - assertThat(entries, arrayContainingInAnyOrder("vars")); } @Test public void cloneModeLibraryPathSecurity() throws Exception { @@ -459,11 +448,11 @@ public static class BasicSCMSource extends SCMSource { GlobalLibraries.get().setLibraries(Collections.singletonList(lc)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); p.setDefinition(new CpsFlowDefinition("@Library('echoing@master') import myecho; myecho()", true)); - SCMBasedRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = false; + LibraryRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = false; WorkflowRun b = r.buildAndAssertSuccess(p); assertFalse(r.jenkins.getWorkspaceFor(p).withSuffix("@libs").isDirectory()); r.assertLogContains("something special", b); - r.assertLogContains("Excluding src/test/ from checkout", b); + r.assertLogContains("Excluding src/test/", b); } @Test public void cloneModeIncludeSrcTest() throws Exception { @@ -480,7 +469,7 @@ public static class BasicSCMSource extends SCMSource { GlobalLibraries.get().setLibraries(Collections.singletonList(lc)); WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); p.setDefinition(new CpsFlowDefinition("@Library('echoing@master') import myecho; myecho()", true)); - SCMBasedRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = true; + LibraryRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = true; WorkflowRun b = r.buildAndAssertSuccess(p); assertFalse(r.jenkins.getWorkspaceFor(p).withSuffix("@libs").isDirectory()); r.assertLogContains("got something special", b); diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml new file mode 100644 index 00000000..2e9bc7ba --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml @@ -0,0 +1,130 @@ + + + + + + + lib + master + + foo + + true + true + 897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8 + + + + + + + master + + + 3ed68a85765cb39641044af8dc4ad436fafe3fdd + + + + master + + + + + 1 + + + + + + …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 + + + + + + git …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 + + + + + + 1 + 1678113342893 + 1678113342911 + 0 + UTF-8 + false + + SUCCESS + + + MAX_SURVIVABILITY + + + flowNode + 36231997 + + + classLoad + 193985514 + + + run + 152858131 + + + parse + 500490530 + + + saveProgram + 38950881 + + + true + 3 + 1:3 + 2 + false + false + + false + + + + 2 + + + origin + +refs/heads/*:refs/remotes/origin/* + …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 + + + + + master + + + false + + + + + false + + + + + + + + + + …/pipeline-groovy-lib-plugin/target/tmp/j h17061459720094902767/workspace/p@libs/999c1ee22db1f1f266cfcd6f657a4e9044467f77fcf8c8e4a377d3099a1d3a75 + …/pipeline-groovy-lib-plugin/target/tmp/j h17061459720094902767/jobs/p/builds/1/changelog13282840290199774426.xml + + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/changelog13282840290199774426.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/changelog13282840290199774426.xml new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8-name.txt b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8-name.txt new file mode 100644 index 00000000..7951405f --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8-name.txt @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8/vars/foo.groovy b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8/vars/foo.groovy new file mode 100644 index 00000000..8f75b987 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8/vars/foo.groovy @@ -0,0 +1 @@ +def call() { echo('called Foo') } \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log new file mode 100644 index 00000000..423b9c8d --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log @@ -0,0 +1,28 @@ +Started +Loading library lib@master +Attempting to resolve master from remote references... + > git --version # timeout=10 + > git --version # 'git version 2.34.1' + > git ls-remote -h -- …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 # timeout=10 +Found match: refs/heads/master revision 3ed68a85765cb39641044af8dc4ad436fafe3fdd +The recommended git tool is: NONE +No credentials specified +Cloning the remote Git repository +Cloning with configured refspecs honoured and without tags +Cloning repository …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 + > git init …/pipeline-groovy-lib-plugin/target/tmp/j h17061459720094902767/workspace/p@libs/999c1ee22db1f1f266cfcd6f657a4e9044467f77fcf8c8e4a377d3099a1d3a75 # timeout=10 +Fetching upstream changes from …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 + > git --version # timeout=10 + > git --version # 'git version 2.34.1' + > git fetch --no-tags --force --progress -- …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 +refs/heads/*:refs/remotes/origin/* # timeout=10 + > git config remote.origin.url …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 # timeout=10 + > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10 +Avoid second fetch +Checking out Revision 3ed68a85765cb39641044af8dc4ad436fafe3fdd (master) + > git config core.sparsecheckout # timeout=10 + > git checkout -f 3ed68a85765cb39641044af8dc4ad436fafe3fdd # timeout=10 +Commit message: "init" +First time build. Skipping changelog. +ha:////4DehqyOFtNH235D4++PGtYgv/4iflo72uInNzFKYe/qgAAAAoh+LCAAAAAAAAP9tjTEOwjAQBM8BClpKHuFItIiK1krDC0x8GCfWnbEdkooX8TX+gCESFVvtrLSa5wtWKcKBo5UdUu8otU4GP9jS5Mixv3geZcdn2TIl9igbHBs2eJyx4YwwR1SwULBGaj0nRzbDRnX6rmuvydanHMu2V1A5c4MHCFXMWcf8hSnC9jqYxPTz/BXAFEIGsfuclm8zQVqFvQAAAA==[Pipeline] Start of Pipeline +ha:////4KmcPvrxnGvS+11mq0cJwM0MifzD3S6+ZqB6eVBoHivtAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQSbaGmsbAmNJ0AWEZb8zwLrbuWJvJp3kLiJlZNMMm+a93rDOic4UbLcG+wdZu14DKOti0+U+lugiXu6ck2YKRguzSSpM+cFJRUDS1gDKwEbgzpQdmgLbIVXD9UGhba9lFS/o4DGdQM8gYlqLiqVL8wJdvexy4Q/z18BzLEA29ce4gfya1RxvAAAAA==[Pipeline] sleep +Sleeping for 3 min 0 sec diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index new file mode 100644 index 00000000..8a1427c8 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index @@ -0,0 +1 @@ +2388 3 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat new file mode 100644 index 00000000..e2bd5c9b Binary files /dev/null and b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat differ diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml new file mode 100644 index 00000000..725bc7eb --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml @@ -0,0 +1,12 @@ + + + + + 2 + + + + 1678113343524 + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml new file mode 100644 index 00000000..3c34d0fe --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml @@ -0,0 +1,26 @@ + + + + + 2 + + 3 + org.jenkinsci.plugins.workflow.steps.SleepStep + + + + + + time + 180 + + + + true + + + 1678113343620 + + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/legacyIds b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/legacyIds new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks new file mode 100644 index 00000000..48ab9e85 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks @@ -0,0 +1 @@ +lastSuccessfulBuild -1 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/config.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/config.xml new file mode 100644 index 00000000..07f78a6f --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/config.xml @@ -0,0 +1,13 @@ + + + false + + + + true + + + false + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber new file mode 100644 index 00000000..0cfbf088 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber @@ -0,0 +1 @@ +2 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml new file mode 100644 index 00000000..33e6c526 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml @@ -0,0 +1,7 @@ + + + + p + 1 + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml new file mode 100644 index 00000000..d0441445 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml @@ -0,0 +1,126 @@ + + + + + + + stuff + master + + true + true + eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b + + + + + + + master + + + 98c9e41033d994442c0105ba3011fe39a57a69b1 + + + + master + + + + + 1 + + + + + + …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 + + + + + + git …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 + + + + + + 1 + 1678379139742 + 1678379139771 + 0 + UTF-8 + false + + SUCCESS + + + MAX_SURVIVABILITY + + + flowNode + 82579725 + + + classLoad + 406679430 + + + run + 370461094 + + + parse + 387067399 + + + saveProgram + 121174695 + + + true + 4 + 1:4 + 2 + false + false + + false + + + + 2 + + + origin + +refs/heads/*:refs/remotes/origin/* + …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 + + + + + master + + + false + + + + + false + + + + + + + + + + …/pipeline-groovy-lib-plugin/target/tmp/j h744515433002142412/workspace/p@libs/f42b7770bb31e81024ba7b8451a6e63e6a1ce3eac92cd7c91e73094d7d85b2b9 + …/pipeline-groovy-lib-plugin/target/tmp/j h744515433002142412/jobs/p/builds/1/changelog5512666869627859761.xml + + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/changelog5512666869627859761.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/changelog5512666869627859761.xml new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b-name.txt b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b-name.txt new file mode 100644 index 00000000..59c5d2b4 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b-name.txt @@ -0,0 +1 @@ +stuff \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b/src/pkg/C.groovy b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b/src/pkg/C.groovy new file mode 100644 index 00000000..6ac9168c --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b/src/pkg/C.groovy @@ -0,0 +1 @@ +package pkg; class C {static void m() {23}} \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log new file mode 100644 index 00000000..97a7e935 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log @@ -0,0 +1,29 @@ +Started +ha:////4ESdDHApaBCDSybNFTtB8QH7JYx/td1ExPJr3dgp1IVpAAAAoh+LCAAAAAAAAP9tjTEOwjAQBM8BClpKHuFItIiK1krDC0x8GCfWnbEdkooX8TX+gCESFVvtrLSa5wtWKcKBo5UdUu8otU4GP9jS5Mixv3geZcdn2TIl9igbHBs2eJyx4YwwR1SwULBGaj0nRzbDRnX6rmuvydanHMu2V1A5c4MHCFXMWcf8hSnC9jqYxPTz/BXAFEIGsfuclm8zQVqFvQAAAA==[Pipeline] Start of Pipeline +ha:////4GmDEP+pf+jq6je4q08twRF2g64HEz1ZmA5RyCc5wqQeAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQSbaGmsbAmNJ0AWEZb8zwLrbuWJvJp3kLiJlZNMMm+a93rDOic4UbLcG+wdZu14DKOti0+U+lugiXu6ck2YKRguzSSpM+cFJRUDS1gDKwEbgzpQdmgLbIVXD9UGhba9lFS/o4DGdQM8gYlqLiqVL8wJdvexy4Q/z18BzLEA29ce4gfya1RxvAAAAA==[Pipeline] library +Loading library stuff@master +Attempting to resolve master from remote references... + > git --version # timeout=10 + > git --version # 'git version 2.34.1' + > git ls-remote -h -- …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 # timeout=10 +Found match: refs/heads/master revision 98c9e41033d994442c0105ba3011fe39a57a69b1 +The recommended git tool is: NONE +No credentials specified +Cloning the remote Git repository +Cloning with configured refspecs honoured and without tags +Cloning repository …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 + > git init …/pipeline-groovy-lib-plugin/target/tmp/j h744515433002142412/workspace/p@libs/f42b7770bb31e81024ba7b8451a6e63e6a1ce3eac92cd7c91e73094d7d85b2b9 # timeout=10 +Fetching upstream changes from …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 + > git --version # timeout=10 + > git --version # 'git version 2.34.1' + > git fetch --no-tags --force --progress -- …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 +refs/heads/*:refs/remotes/origin/* # timeout=10 + > git config remote.origin.url …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 # timeout=10 + > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10 +Avoid second fetch +Checking out Revision 98c9e41033d994442c0105ba3011fe39a57a69b1 (master) + > git config core.sparsecheckout # timeout=10 + > git checkout -f 98c9e41033d994442c0105ba3011fe39a57a69b1 # timeout=10 +Commit message: "init" +First time build. Skipping changelog. +ha:////4JPM8UkGCuomMvb51Uo5DyzahUcbxePS+FWXyxySXcOwAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQSbGDtjZUtoPAGyiLDkfxZYdytP5NW8g8RNrJxkknnTvNcb1jnBiZLl3mDvMGvHYxhtXXyi1N8CTdzTlWvCTMFwaSZJnTkvKKkYWMIaWAnYGNSBskNbYCu8eqg2KLTtpaT6HQU0rhvgCUxUc1GpfGFOsLuPXSb8ef4KYI4F2L72ED81/RU+vAAAAA==[Pipeline] sleep +Sleeping for 3 min 0 sec diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index new file mode 100644 index 00000000..45645b44 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index @@ -0,0 +1,3 @@ +622 3 +2385 +2685 4 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat new file mode 100644 index 00000000..8a44b773 Binary files /dev/null and b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat differ diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml new file mode 100644 index 00000000..bf6dd7fd --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml @@ -0,0 +1,12 @@ + + + + + 2 + + + + 1678379140348 + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml new file mode 100644 index 00000000..9cf66a5b --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml @@ -0,0 +1,26 @@ + + + + + 2 + + 3 + org.jenkinsci.plugins.workflow.libs.LibraryStep + + + + + + identifier + stuff@master + + + + true + + + 1678379140501 + + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/4.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/4.xml new file mode 100644 index 00000000..7cf17cd2 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/4.xml @@ -0,0 +1,26 @@ + + + + + 3 + + 4 + org.jenkinsci.plugins.workflow.steps.SleepStep + + + + + + time + 180 + + + + true + + + 1678379141028 + + + + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/legacyIds b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/legacyIds new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks new file mode 100644 index 00000000..48ab9e85 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks @@ -0,0 +1 @@ +lastSuccessfulBuild -1 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/config.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/config.xml new file mode 100644 index 00000000..69201d5e --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/config.xml @@ -0,0 +1,11 @@ + + + false + + + + true + + + false + \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber new file mode 100644 index 00000000..0cfbf088 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber @@ -0,0 +1 @@ +2 diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml new file mode 100644 index 00000000..33e6c526 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml @@ -0,0 +1,7 @@ + + + + p + 1 + + \ No newline at end of file