diff --git a/langstream-cli/src/main/java/ai/langstream/cli/commands/docker/LocalRunApplicationCmd.java b/langstream-cli/src/main/java/ai/langstream/cli/commands/docker/LocalRunApplicationCmd.java index 8256d0a95..63d52e844 100644 --- a/langstream-cli/src/main/java/ai/langstream/cli/commands/docker/LocalRunApplicationCmd.java +++ b/langstream-cli/src/main/java/ai/langstream/cli/commands/docker/LocalRunApplicationCmd.java @@ -44,6 +44,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.input.Tailer; import org.apache.commons.io.input.TailerListener; +import org.apache.commons.lang3.SystemUtils; import picocli.CommandLine; @CommandLine.Command(name = "run", header = "Run on a docker container a LangStream application") @@ -302,14 +303,9 @@ private void executeOnDocker( boolean startDatabase, boolean dryRun) throws Exception { - final File appTmp = generateTempFile("app"); - FileUtils.copyDirectory(appDirectory, appTmp); - makeDirOrFileReadable(appTmp); - File tmpInstanceFile = createReadableTempFile("instance", instanceContents); - File tmpSecretsFile = null; - if (secretsContents != null) { - tmpSecretsFile = createReadableTempFile("secrets", secretsContents); - } + final File appTmp = prepareAppDirectory(appDirectory); + File tmpInstanceFile = prepareInstanceFile(instanceContents); + File tmpSecretsFile = prepareSecretsFile(secretsContents); String imageName = dockerImageName + ":" + dockerImageVersion; List commandLine = new ArrayList<>(); commandLine.add(dockerCommand); @@ -403,6 +399,33 @@ private void executeOnDocker( } } + private File prepareSecretsFile(String secretsContents) throws IOException { + File tmpSecretsFile = null; + if (secretsContents != null) { + tmpSecretsFile = createReadableTempFile("secrets", secretsContents); + } + return tmpSecretsFile; + } + + private File prepareInstanceFile(String instanceContents) throws IOException { + return createReadableTempFile("instance", instanceContents); + } + + private File prepareAppDirectory(File appDirectory) throws IOException { + // depending on the docker engine, we should ensure the permissions are properly set. + // On MacOs, Docker runs on a VM, so the user mapping between the host and the container is + // performed by the engine. + // On Linux, we need to make sure all the mounted volumes are readable from others because + // the docker container runs with a different user than the file owner. + if (SystemUtils.IS_OS_MAC) { + return appDirectory; + } + final File appTmp = generateTempFile("app"); + FileUtils.copyDirectory(appDirectory, appTmp); + makeDirOrFileReadable(appTmp); + return appTmp; + } + private static File createReadableTempFile(String prefix, String instanceContents) throws IOException { File tempFile = generateTempFile(prefix); @@ -416,8 +439,12 @@ private static void makeDirOrFileReadable(File file) throws IOException { for (File child : file.listFiles()) { makeDirOrFileReadable(child); } + Files.setPosixFilePermissions( + file.toPath(), PosixFilePermissions.fromString("rwxr-xr-x")); + } else { + Files.setPosixFilePermissions( + file.toPath(), PosixFilePermissions.fromString("rw-r--r--")); } - Files.setPosixFilePermissions(file.toPath(), PosixFilePermissions.fromString("rw-r--r--")); } @SneakyThrows diff --git a/langstream-cli/src/test/java/ai/langstream/cli/commands/docker/LocalRunApplicationCmdTest.java b/langstream-cli/src/test/java/ai/langstream/cli/commands/docker/LocalRunApplicationCmdTest.java index 733de0551..ff9a16651 100644 --- a/langstream-cli/src/test/java/ai/langstream/cli/commands/docker/LocalRunApplicationCmdTest.java +++ b/langstream-cli/src/test/java/ai/langstream/cli/commands/docker/LocalRunApplicationCmdTest.java @@ -20,8 +20,6 @@ import ai.langstream.cli.NamedProfile; import ai.langstream.cli.commands.applications.CommandTestBase; import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermission; @@ -31,6 +29,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.apache.commons.lang3.SystemUtils; import org.junit.jupiter.api.Test; class LocalRunApplicationCmdTest extends CommandTestBase { @@ -38,8 +37,11 @@ class LocalRunApplicationCmdTest extends CommandTestBase { @Test void testArgs() throws Exception { final Path tempDir = Files.createTempDirectory(this.tempDir, "langstream"); + final Path appConfigFile = Files.createTempFile(tempDir, "configuration", ".yaml"); + Files.writeString(appConfigFile, "configuration: {}"); + final Path secrets = Files.createTempFile("langstream", ".yaml"); - Files.write(secrets, "secrets: []".getBytes(StandardCharsets.UTF_8)); + Files.writeString(secrets, "secrets: []"); final String appDir = tempDir.toFile().getAbsolutePath(); CommandResult result = @@ -74,25 +76,45 @@ void testArgs() throws Exception { final List volumes = extractVolumes(lastLine); assertEquals(3, volumes.size()); - volumes.forEach( - volume -> { - final String hostPath = volume.split(":")[0]; - final File file = new File(hostPath); - assertTrue(file.exists()); - final Path langstreamTmp = - Path.of(System.getProperty("user.home"), ".langstream", "tmp"); + for (String volume : volumes) { + + final String hostPath = volume.split(":")[0]; + final File file = new File(hostPath); + assertTrue(file.exists()); + final Path langstreamTmp = + Path.of(System.getProperty("user.home"), ".langstream", "tmp"); + + final Set posixFilePermissions = + Files.getPosixFilePermissions(file.toPath()); + if (file.isDirectory()) { + if (SystemUtils.IS_OS_MAC) { + assertNotEquals(langstreamTmp, file.toPath().getParent()); + } else { assertEquals(langstreamTmp, file.toPath().getParent()); - final Set posixFilePermissions; - try { - posixFilePermissions = Files.getPosixFilePermissions(file.toPath()); - assertTrue(posixFilePermissions.contains(PosixFilePermission.OTHERS_READ)); - assertTrue(posixFilePermissions.contains(PosixFilePermission.OWNER_READ)); - assertTrue(posixFilePermissions.contains(PosixFilePermission.GROUP_READ)); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + assertEquals( + Set.of( + PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.GROUP_READ, + PosixFilePermission.OTHERS_READ, + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.GROUP_EXECUTE, + PosixFilePermission.OTHERS_EXECUTE), + posixFilePermissions); + } + final String[] children = file.list(); + assertEquals(1, children.length); + if (!SystemUtils.IS_OS_MAC) { + assertFileReadable( + Files.getPosixFilePermissions( + Path.of(file.getAbsolutePath(), children[0]))); + } + } else { + assertEquals(langstreamTmp, file.toPath().getParent()); + assertFileReadable(posixFilePermissions); + } + } final NamedProfile namedProfile = getConfig().getProfiles().get("local-docker-run"); assertNotNull(namedProfile); assertEquals("default", namedProfile.getTenant()); @@ -100,6 +122,16 @@ void testArgs() throws Exception { assertEquals("ws://localhost:8091", namedProfile.getApiGatewayUrl()); } + private void assertFileReadable(Set posixFilePermissions) { + assertEquals( + Set.of( + PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.GROUP_READ, + PosixFilePermission.OTHERS_READ), + posixFilePermissions); + } + private static List extractVolumes(String input) { List volumes = new ArrayList<>(); Pattern pattern = Pattern.compile("-v\\s+([^\\s]+)");