Skip to content

Commit 2428a84

Browse files
committed
Fix project archive directory entries for Rundeck 6 async import
Rundeck 6's AsyncImportService expects explicit directory entries (with trailing /) in project archives. Without them, directories like "jobs" extract as zero-byte files, causing "Not a directory" errors during import. Changes: - Collect all parent directories from file paths - Create JarEntry objects with trailing / for each directory - Write directory entries before file entries, ordered shallow-to-deep - Normalize paths to use forward slashes This ensures archives are compatible with both Rundeck 5 and 6 import mechanisms.
1 parent 570e01a commit 2428a84

1 file changed

Lines changed: 35 additions & 12 deletions

File tree

functional-test/src/test/groovy/functional/util/TestUtil.groovy

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import com.jcraft.jsch.JSch
44
import com.jcraft.jsch.KeyPair
55
import org.rundeck.client.api.model.ExecLog
66

7+
import groovy.io.FileType
8+
79
import java.nio.file.Files
10+
import java.nio.file.Path
811
import java.nio.file.attribute.PosixFilePermission
912
import java.text.SimpleDateFormat
1013
import java.util.jar.JarEntry
@@ -35,19 +38,39 @@ class TestUtil {
3538
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX").format(new Date())
3639
)
3740

41+
Path base = projectArchiveDirectory.toPath()
42+
List<File> filesOnly = []
43+
projectArchiveDirectory.eachFileRecurse(FileType.FILES) { f -> filesOnly.add(f) }
44+
45+
// Rundeck 6 async import expects directory entries with trailing /. Otherwise "jobs" can extract as a
46+
// zero-byte file and AsyncImportService fails with "Not a directory".
47+
Set<String> directoryEntries = new LinkedHashSet<>()
48+
for (File f : filesOnly) {
49+
String rel = base.relativize(f.toPath()).toString().replace('\\', '/')
50+
String[] parts = rel.split('/')
51+
for (int i = 0; i < parts.length - 1; i++) {
52+
directoryEntries.add((parts[0..i].join('/') + '/') as String)
53+
}
54+
}
55+
List<String> dirsOrdered = new ArrayList<>(directoryEntries)
56+
dirsOrdered.sort { a, b ->
57+
int da = a.count('/')
58+
int db = b.count('/')
59+
da != db ? da <=> db : a <=> b
60+
}
61+
3862
tempFile.withOutputStream { os ->
39-
def jos = new JarOutputStream(os, manifest)
40-
41-
jos.withCloseable { jarOutputStream ->
42-
43-
projectArchiveDirectory.eachFileRecurse { file ->
44-
def entry = new JarEntry(projectArchiveDirectory.toPath().relativize(file.toPath()).toString())
45-
jarOutputStream.putNextEntry(entry)
46-
if (file.isFile()) {
47-
file.withInputStream { is ->
48-
jarOutputStream << is
49-
}
50-
}
63+
JarOutputStream jos = new JarOutputStream(os, manifest)
64+
jos.withCloseable { JarOutputStream jarOutputStream ->
65+
for (String dir : dirsOrdered) {
66+
jarOutputStream.putNextEntry(new JarEntry(dir))
67+
jarOutputStream.closeEntry()
68+
}
69+
for (File file : filesOnly) {
70+
String entryName = base.relativize(file.toPath()).toString().replace('\\', '/')
71+
jarOutputStream.putNextEntry(new JarEntry(entryName))
72+
file.withInputStream { is -> jarOutputStream << is }
73+
jarOutputStream.closeEntry()
5174
}
5275
}
5376
}

0 commit comments

Comments
 (0)