From 56b9a3f726a466d28c727721215f7bbdbf2dc162 Mon Sep 17 00:00:00 2001 From: Marvin Date: Sun, 30 Apr 2023 22:40:32 +0200 Subject: [PATCH] feat: implement a method to delete a directory from a template. This change allows developers to directly delete a directory and all its containing files and directories from a given service template. As an additional functionality, this method is also exposed to the HTTP Rest API much like the file deletion method already is. --- .../driver/template/TemplateStorage.java | 24 +++++++++++++++++++ .../rest/v2/V2HttpHandlerTemplate.java | 19 +++++++++++++++ .../modules/s3/S3TemplateStorage.java | 23 ++++++++++++++++++ .../modules/sftp/SFTPTemplateStorage.java | 9 +++++++ .../node/template/LocalTemplateStorage.java | 11 +++++++++ 5 files changed, 86 insertions(+) diff --git a/driver/src/main/java/eu/cloudnetservice/driver/template/TemplateStorage.java b/driver/src/main/java/eu/cloudnetservice/driver/template/TemplateStorage.java index f9311f2ce1..25366a9187 100644 --- a/driver/src/main/java/eu/cloudnetservice/driver/template/TemplateStorage.java +++ b/driver/src/main/java/eu/cloudnetservice/driver/template/TemplateStorage.java @@ -220,6 +220,17 @@ default boolean deployDirectory(@NonNull ServiceTemplate target, @NonNull Path d */ boolean deleteFile(@NonNull ServiceTemplate template, @NonNull String path); + /** + * Deletes the given directory at the given path in the given template in this storage. + * This method recursively deletes all files and directories inside the given directory. + * + * @param template the template in which the directory to delete is located in. + * @param path the path to the directory in the template to delete. + * @return true if the directory at the given path was deleted successfully, false otherwise. + * @throws NullPointerException if the given template or path is null. + */ + boolean deleteDirectory(@NonNull ServiceTemplate template, @NonNull String path); + /** * Opens a new input stream to read the content of the file at the given path in the given template in this storage. * This method returns null if either the file doesn't exist or is a directory. @@ -480,6 +491,19 @@ default boolean deployDirectory(@NonNull ServiceTemplate target, @NonNull Path d return Task.supply(() -> this.deleteFile(template, path)); } + /** + * Deletes the given directory at the given path in the given template in this storage. This method recursively + * deletes all files and directories inside the given directory. + * + * @param template the template in which the directory to delete is located in. + * @param path the path to the directory in the template to delete. + * @return a task completed with true if the directory at the given path was deleted successfully, false otherwise. + * @throws NullPointerException if the given template or path is null. + */ + default @NonNull Task deleteDirectoryAsync(@NonNull ServiceTemplate template, @NonNull String path) { + return Task.supply(() -> this.deleteDirectory(template, path)); + } + /** * Opens a new input stream to read the content of the file at the given path in the given template in this storage. * This method returns null if either the file doesn't exist or is a directory. diff --git a/modules/rest/src/main/java/eu/cloudnetservice/modules/rest/v2/V2HttpHandlerTemplate.java b/modules/rest/src/main/java/eu/cloudnetservice/modules/rest/v2/V2HttpHandlerTemplate.java index 7a32c33feb..7d042b1aa2 100644 --- a/modules/rest/src/main/java/eu/cloudnetservice/modules/rest/v2/V2HttpHandlerTemplate.java +++ b/modules/rest/src/main/java/eu/cloudnetservice/modules/rest/v2/V2HttpHandlerTemplate.java @@ -238,6 +238,25 @@ private void handleFileDeleteRequest( }); } + @BearerAuth + @HttpRequestHandler(paths = "/api/v2/template/{storage}/{prefix}/{name}/directory", methods = "DELETE") + private void handleDirectoryDeleteRequest( + @NonNull HttpContext context, + @NonNull @RequestPathParam("storage") String storageName, + @NonNull @RequestPathParam("prefix") String prefix, + @NonNull @RequestPathParam("name") String templateName, + @NonNull @FirstRequestQueryParam("path") String path + ) { + this.handleWithTemplateContext(context, storageName, prefix, templateName, (template, storage) -> { + var status = storage.deleteDirectory(template, path); + this.ok(context) + .body(status ? this.success().toString() : this.failure().toString()) + .context() + .closeAfter(true) + .cancelNext(true); + }); + } + @BearerAuth @HttpRequestHandler(paths = "/api/v2/template/{storage}/{prefix}/{name}", methods = "DELETE") private void handleTemplateDeleteRequest( diff --git a/modules/storage-s3/src/main/java/eu/cloudnetservice/modules/s3/S3TemplateStorage.java b/modules/storage-s3/src/main/java/eu/cloudnetservice/modules/s3/S3TemplateStorage.java index 5538380a56..f1f05da719 100644 --- a/modules/storage-s3/src/main/java/eu/cloudnetservice/modules/s3/S3TemplateStorage.java +++ b/modules/storage-s3/src/main/java/eu/cloudnetservice/modules/s3/S3TemplateStorage.java @@ -349,6 +349,29 @@ public boolean deleteFile(@NonNull ServiceTemplate template, @NonNull String pat } } + @Override + public boolean deleteDirectory(@NonNull ServiceTemplate template, @NonNull String path) { + try { + // get the contents we want to delete + Set toDelete = new HashSet<>(); + this.listAllObjects( + this.getBucketPath(template, path), + null, + object -> toDelete.add(ObjectIdentifier.builder().key(object.key()).build())); + + // build the delete request + var deleteRequest = DeleteObjectsRequest.builder() + .bucket(this.config().bucket()) + .delete(Delete.builder().quiet(true).objects(toDelete).build()) + .build(); + this.client.deleteObjects(deleteRequest); + // success + return true; + } catch (Exception exception) { + return false; + } + } + @Override public @Nullable InputStream newInputStream(@NonNull ServiceTemplate template, @NonNull String path) { try { diff --git a/modules/storage-sftp/src/main/java/eu/cloudnetservice/modules/sftp/SFTPTemplateStorage.java b/modules/storage-sftp/src/main/java/eu/cloudnetservice/modules/sftp/SFTPTemplateStorage.java index b65cfcace3..08612079d7 100644 --- a/modules/storage-sftp/src/main/java/eu/cloudnetservice/modules/sftp/SFTPTemplateStorage.java +++ b/modules/storage-sftp/src/main/java/eu/cloudnetservice/modules/sftp/SFTPTemplateStorage.java @@ -273,6 +273,15 @@ public boolean deleteFile(@NonNull ServiceTemplate template, @NonNull String pat }, false); } + @Override + public boolean deleteDirectory(@NonNull ServiceTemplate template, @NonNull String path) { + return this.executeWithClient(client -> { + this.deleteDir(client, this.constructRemotePath(template, path)); + client.rmdir(this.constructRemotePath(template, path)); + return true; + }, false); + } + @Override public @Nullable InputStream newInputStream(@NonNull ServiceTemplate st, @NonNull String path) throws IOException { var client = this.pool.takeClient(); diff --git a/node/src/main/java/eu/cloudnetservice/node/template/LocalTemplateStorage.java b/node/src/main/java/eu/cloudnetservice/node/template/LocalTemplateStorage.java index daf8235b96..69040f3fe8 100644 --- a/node/src/main/java/eu/cloudnetservice/node/template/LocalTemplateStorage.java +++ b/node/src/main/java/eu/cloudnetservice/node/template/LocalTemplateStorage.java @@ -193,6 +193,17 @@ public boolean deleteFile(@NonNull ServiceTemplate template, @NonNull String pat return false; } + @Override + public boolean deleteDirectory(@NonNull ServiceTemplate template, @NonNull String path) { + var dirPath = this.getTemplatePath(template).resolve(path); + if (Files.exists(dirPath) && Files.isDirectory(dirPath)) { + FileUtil.delete(dirPath); + return true; + } + + return false; + } + @Override public @Nullable InputStream newInputStream( @NonNull ServiceTemplate template,