diff --git a/CHANGELOG.md b/CHANGELOG.md index 045bfa92a9dee..fdbff5c42d975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Create and attach interclusterTest and yamlRestTest code coverage reports to gradle check task([#19165](https://github.com/opensearch-project/OpenSearch/pull/19165)) - Optimized date histogram aggregations by preventing unnecessary object allocations in date rounding utils ([19088](https://github.com/opensearch-project/OpenSearch/pull/19088)) - Optimize source conversion in gRPC search hits using zero-copy BytesRef ([#19280](https://github.com/opensearch-project/OpenSearch/pull/19280)) +- Allow plugins to copy folders into their config dir during installation ([#19343](https://github.com/opensearch-project/OpenSearch/pull/19343)) - Add failureaccess as runtime dependency to transport-grpc module ([#19339](https://github.com/opensearch-project/OpenSearch/pull/19339)) ### Fixed diff --git a/distribution/tools/plugin-cli/src/main/java/org/opensearch/tools/cli/plugin/InstallPluginCommand.java b/distribution/tools/plugin-cli/src/main/java/org/opensearch/tools/cli/plugin/InstallPluginCommand.java index c71728056b4c4..4f69157545a22 100644 --- a/distribution/tools/plugin-cli/src/main/java/org/opensearch/tools/cli/plugin/InstallPluginCommand.java +++ b/distribution/tools/plugin-cli/src/main/java/org/opensearch/tools/cli/plugin/InstallPluginCommand.java @@ -960,16 +960,13 @@ private void installConfig(PluginInfo info, Path tmpConfigDir, Path destConfigDi try (DirectoryStream stream = Files.newDirectoryStream(tmpConfigDir)) { for (Path srcFile : stream) { - if (Files.isDirectory(srcFile)) { - throw new UserException(PLUGIN_MALFORMED, "Directories not allowed in config dir for plugin " + info.getName()); - } - Path destFile = destConfigDir.resolve(tmpConfigDir.relativize(srcFile)); if (Files.exists(destFile) == false) { - Files.copy(srcFile, destFile); - setFileAttributes(destFile, CONFIG_FILES_PERMS); - if (destConfigDirAttributes != null) { - setOwnerGroup(destFile, destConfigDirAttributes); + if (Files.isDirectory(srcFile)) { + copyWithPermissions(srcFile, destFile, CONFIG_DIR_PERMS, destConfigDirAttributes); + copyDirectoryRecursively(srcFile, destFile, destConfigDirAttributes); + } else { + copyWithPermissions(srcFile, destFile, CONFIG_FILES_PERMS, destConfigDirAttributes); } } } @@ -995,6 +992,42 @@ private static void setFileAttributes(final Path path, final Set permissions, + PosixFileAttributes attributes + ) throws IOException { + Files.copy(srcFile, destFile); + setFileAttributes(destFile, permissions); + if (attributes != null) { + setOwnerGroup(destFile, attributes); + } + } + + /** + * Recursively copies directory contents from source to destination. + */ + private static void copyDirectoryRecursively(Path srcDir, Path destDir, PosixFileAttributes destConfigDirAttributes) + throws IOException { + try (DirectoryStream stream = Files.newDirectoryStream(srcDir)) { + for (Path srcFile : stream) { + Path destFile = destDir.resolve(srcDir.relativize(srcFile)); + if (Files.exists(destFile) == false) { + if (Files.isDirectory(srcFile)) { + copyWithPermissions(srcFile, destFile, CONFIG_DIR_PERMS, destConfigDirAttributes); + copyDirectoryRecursively(srcFile, destFile, destConfigDirAttributes); + } else { + copyWithPermissions(srcFile, destFile, CONFIG_FILES_PERMS, destConfigDirAttributes); + } + } + } + } + } + private final List pathsToDeleteOnShutdown = new ArrayList<>(); @Override diff --git a/distribution/tools/plugin-cli/src/test/java/org/opensearch/tools/cli/plugin/InstallPluginCommandTests.java b/distribution/tools/plugin-cli/src/test/java/org/opensearch/tools/cli/plugin/InstallPluginCommandTests.java index 57cf65a4a2c51..e2cd36f7bb8b9 100644 --- a/distribution/tools/plugin-cli/src/test/java/org/opensearch/tools/cli/plugin/InstallPluginCommandTests.java +++ b/distribution/tools/plugin-cli/src/test/java/org/opensearch/tools/cli/plugin/InstallPluginCommandTests.java @@ -433,8 +433,6 @@ void assertConfigAndBin(String name, Path original, Environment env) throws IOEx try (DirectoryStream stream = Files.newDirectoryStream(configDir)) { for (Path file : stream) { - assertFalse("not a dir", Files.isDirectory(file)); - if (isPosix) { PosixFileAttributes attributes = Files.readAttributes(file, PosixFileAttributes.class); if (user != null) { @@ -803,9 +801,14 @@ public void testConfigContainsDir() throws Exception { Files.createDirectories(dirInConfigDir); Files.createFile(dirInConfigDir.resolve("myconfig.yml")); String pluginZip = createPluginUrl("fake", pluginDir); - UserException e = expectThrows(UserException.class, () -> installPlugin(pluginZip, env.v1())); - assertTrue(e.getMessage(), e.getMessage().contains("Directories not allowed in config dir for plugin")); - assertInstallCleaned(env.v2()); + installPlugin(pluginZip, env.v1()); + assertPlugin("fake", pluginDir, env.v2()); + + // Verify the directory and file were installed + Path installedConfigDir = env.v2().configDir().resolve("fake").resolve("foo"); + assertTrue(Files.exists(installedConfigDir)); + assertTrue(Files.isDirectory(installedConfigDir)); + assertTrue(Files.exists(installedConfigDir.resolve("myconfig.yml"))); } public void testMissingDescriptor() throws Exception {