fix(plugins): properly handle POSIX permissions for Docker task runner

Set POSIX file permissions when uploading/download working-dir files
from and to Docker container
This commit is contained in:
Florian Hussonnois
2025-05-15 10:21:53 +02:00
committed by Florian Hussonnois
parent 09d91034ab
commit 59e631f048
3 changed files with 112 additions and 3 deletions

View File

@@ -0,0 +1,68 @@
package io.kestra.core.utils;
import java.nio.file.attribute.PosixFilePermission;
import java.util.HashSet;
import java.util.Set;
/**
* Utility method for converting Unix file mode into PosixFilePermission.
*/
public final class UnixModeToPosixFilePermissions {
public static Set<PosixFilePermission> toPosixPermissions(int mode) {
Set<PosixFilePermission> permissions = new HashSet<>();
// Check owner permissions
if ((mode & 0400) != 0) {
permissions.add(PosixFilePermission.OWNER_READ);
}
if ((mode & 0200) != 0) {
permissions.add(PosixFilePermission.OWNER_WRITE);
}
if ((mode & 0100) != 0) {
permissions.add(PosixFilePermission.OWNER_EXECUTE);
}
// Check group permissions
if ((mode & 0040) != 0) {
permissions.add(PosixFilePermission.GROUP_READ);
}
if ((mode & 0020) != 0) {
permissions.add(PosixFilePermission.GROUP_WRITE);
}
if ((mode & 0010) != 0) {
permissions.add(PosixFilePermission.GROUP_EXECUTE);
}
// Check others permissions
if ((mode & 0004) != 0) {
permissions.add(PosixFilePermission.OTHERS_READ);
}
if ((mode & 0002) != 0) {
permissions.add(PosixFilePermission.OTHERS_WRITE);
}
if ((mode & 0001) != 0) {
permissions.add(PosixFilePermission.OTHERS_EXECUTE);
}
return permissions;
}
public static int fromPosixFilePermissions(final Set<PosixFilePermission> perms) {
int mode = 0;
for (PosixFilePermission perm : perms) {
switch (perm) {
case OWNER_READ: mode |= 0400; break;
case OWNER_WRITE: mode |= 0200; break;
case OWNER_EXECUTE: mode |= 0100; break;
case GROUP_READ: mode |= 0040; break;
case GROUP_WRITE: mode |= 0020; break;
case GROUP_EXECUTE: mode |= 0010; break;
case OTHERS_READ: mode |= 0004; break;
case OTHERS_WRITE: mode |= 0002; break;
case OTHERS_EXECUTE:mode |= 0001; break;
}
}
return mode;
}
}

View File

@@ -0,0 +1,28 @@
package io.kestra.core.utils;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.junit.jupiter.api.Test;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;
import static java.nio.file.attribute.PosixFilePermission.*;
import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
import static org.assertj.core.api.Assertions.assertThat;
class UnixModeToPosixFilePermissionsTest {
@Test
void shouldReturnPosixFilePermissions() {
assertThat(PosixFilePermissions.toString(UnixModeToPosixFilePermissions.toPosixPermissions(Integer.parseInt("700", 8)))).isEqualTo("rwx------");
assertThat(PosixFilePermissions.toString(UnixModeToPosixFilePermissions.toPosixPermissions(Integer.parseInt("620", 8)))).isEqualTo("rw--w----");
assertThat(PosixFilePermissions.toString(UnixModeToPosixFilePermissions.toPosixPermissions(Integer.parseInt("777", 8)))).isEqualTo("rwxrwxrwx");
assertThat(PosixFilePermissions.toString(UnixModeToPosixFilePermissions.toPosixPermissions(TarArchiveEntry.DEFAULT_FILE_MODE))).isEqualTo("rw-r--r--");
}
@Test
void shouldReturnPosixFilePermissionsFromString() {
assertThat(Integer.toOctalString(UnixModeToPosixFilePermissions.fromPosixFilePermissions(Set.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)))).isEqualTo("700");
assertThat(Integer.toOctalString(UnixModeToPosixFilePermissions.fromPosixFilePermissions(Set.of(OWNER_READ, OWNER_WRITE, GROUP_WRITE)))).isEqualTo("620");
}
}

View File

@@ -22,13 +22,13 @@ import io.kestra.core.runners.RunContext;
import io.kestra.core.utils.Await;
import io.kestra.core.utils.ListUtils;
import io.kestra.core.utils.RetryUtils;
import io.kestra.core.utils.UnixModeToPosixFilePermissions;
import io.kestra.plugin.scripts.exec.scripts.models.DockerOptions;
import io.micronaut.core.convert.format.ReadableBytesTypeConverter;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
@@ -44,6 +44,7 @@ import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.PosixFilePermission;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -406,6 +407,13 @@ public class Docker extends TaskRunner<Docker.DockerTaskRunnerDetailResult> {
for (Path file: relativeWorkingDirectoryFilesPaths) {
Path resolvedFile = runContext.workingDir().resolve(file);
TarArchiveEntry entry = out.createArchiveEntry(resolvedFile.toFile(), file.toString());
// Preserve POSIX permissions if supported
try {
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(resolvedFile);
entry.setMode(UnixModeToPosixFilePermissions.fromPosixFilePermissions(perms));
} catch (UnsupportedOperationException | IOException ignore) {
// Skipping unix file permission
}
out.putArchiveEntry(entry);
if (!Files.isDirectory(resolvedFile)) {
try (InputStream fis = Files.newInputStream(resolvedFile)) {
@@ -589,7 +597,7 @@ public class Docker extends TaskRunner<Docker.DockerTaskRunnerDetailResult> {
CopyArchiveFromContainerCmd copyArchiveFromContainerCmd = dockerClient.copyArchiveFromContainerCmd(execId, windowsToUnixPath(taskCommands.getWorkingDirectory().toString()));
try (InputStream is = copyArchiveFromContainerCmd.exec();
TarArchiveInputStream tar = new TarArchiveInputStream(is)) {
ArchiveEntry entry;
TarArchiveEntry entry;
while ((entry = tar.getNextEntry()) != null) {
// each entry contains the working directory as the first part, we need to remove it
Path extractTo = runContext.workingDir().resolve(Path.of(entry.getName().substring(runContext.workingDir().id().length() + 1)));
@@ -599,6 +607,11 @@ public class Docker extends TaskRunner<Docker.DockerTaskRunnerDetailResult> {
}
} else {
Files.copy(tar, extractTo, StandardCopyOption.REPLACE_EXISTING);
try {
Files.setPosixFilePermissions(extractTo, UnixModeToPosixFilePermissions.toPosixPermissions(entry.getMode()));
} catch (UnsupportedOperationException | IOException e) {
// File system does not support POSIX permissions (e.g., Windows)
}
}
}
}