From a07bec0833891e67e2a5e2b61f3513a87687e9c6 Mon Sep 17 00:00:00 2001 From: Marvin Date: Sun, 30 Apr 2023 22:40:32 +0200 Subject: [PATCH 1/3] 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, From bcfc6d954dc6e18f5c8d9e207d7827dff49a043c Mon Sep 17 00:00:00 2001 From: Marvin Date: Sun, 9 Jul 2023 17:29:03 +0200 Subject: [PATCH 2/3] feat: add test methods for the newly added method to delete directories from templates. --- .../modules/s3/S3TemplateStorageTest.java | 11 +++++++++++ .../modules/sftp/SFTPTemplateStorage.java | 1 - .../modules/sftp/SFTPTemplateStorageTest.java | 11 +++++++++++ .../node/template/LocalTemplateStorageTest.java | 11 +++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/modules/storage-s3/src/test/java/eu/cloudnetservice/modules/s3/S3TemplateStorageTest.java b/modules/storage-s3/src/test/java/eu/cloudnetservice/modules/s3/S3TemplateStorageTest.java index 19344d3fbb..cc95083130 100644 --- a/modules/storage-s3/src/test/java/eu/cloudnetservice/modules/s3/S3TemplateStorageTest.java +++ b/modules/storage-s3/src/test/java/eu/cloudnetservice/modules/s3/S3TemplateStorageTest.java @@ -201,4 +201,15 @@ void testTemplateDelete() { Assertions.assertFalse(storage.contains(TEMPLATE)); Assertions.assertFalse(storage.hasFile(TEMPLATE, "test.txt")); } + + @Test + @Order(120) + void testDeleteDirectory() { + var directory = "hello"; + Assertions.assertTrue(storage.createDirectory(TEMPLATE, directory)); + Assertions.assertTrue(storage.createFile(TEMPLATE, directory + "/test.txt")); + Assertions.assertTrue(storage.hasFile(TEMPLATE, directory + "/test.txt")); + Assertions.assertTrue(storage.deleteDirectory(TEMPLATE, directory)); + Assertions.assertFalse(storage.hasFile(TEMPLATE, directory + "/test.txt")); + } } 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 08612079d7..83fa98cd97 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 @@ -277,7 +277,6 @@ public boolean deleteFile(@NonNull ServiceTemplate template, @NonNull String pat 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); } diff --git a/modules/storage-sftp/src/test/java/eu/cloudnetservice/modules/sftp/SFTPTemplateStorageTest.java b/modules/storage-sftp/src/test/java/eu/cloudnetservice/modules/sftp/SFTPTemplateStorageTest.java index c7aad15f4a..a7d394388d 100644 --- a/modules/storage-sftp/src/test/java/eu/cloudnetservice/modules/sftp/SFTPTemplateStorageTest.java +++ b/modules/storage-sftp/src/test/java/eu/cloudnetservice/modules/sftp/SFTPTemplateStorageTest.java @@ -193,4 +193,15 @@ void testTemplateDelete() { Assertions.assertFalse(storage.contains(TEMPLATE)); Assertions.assertFalse(storage.hasFile(TEMPLATE, "test.txt")); } + + @Test + @Order(120) + void testDeleteDirectory() { + var directory = "hello"; + Assertions.assertTrue(storage.createDirectory(TEMPLATE, directory)); + Assertions.assertTrue(storage.createFile(TEMPLATE, directory + "/test.txt")); + Assertions.assertTrue(storage.hasFile(TEMPLATE, directory + "/test.txt")); + Assertions.assertTrue(storage.deleteDirectory(TEMPLATE, directory)); + Assertions.assertFalse(storage.hasFile(TEMPLATE, directory + "/test.txt")); + } } diff --git a/node/src/test/java/eu/cloudnetservice/node/template/LocalTemplateStorageTest.java b/node/src/test/java/eu/cloudnetservice/node/template/LocalTemplateStorageTest.java index 6904911f7c..0f41ea5f3c 100644 --- a/node/src/test/java/eu/cloudnetservice/node/template/LocalTemplateStorageTest.java +++ b/node/src/test/java/eu/cloudnetservice/node/template/LocalTemplateStorageTest.java @@ -177,4 +177,15 @@ void testTemplateDelete() { Assertions.assertFalse(storage.contains(TEMPLATE)); Assertions.assertFalse(storage.hasFile(TEMPLATE, "test.txt")); } + + @Test + @Order(120) + void testDeleteDirectory() { + var directory = "hello"; + Assertions.assertTrue(storage.createDirectory(TEMPLATE, directory)); + Assertions.assertTrue(storage.createFile(TEMPLATE, directory + "/test.txt")); + Assertions.assertTrue(storage.hasFile(TEMPLATE, directory + "/test.txt")); + Assertions.assertTrue(storage.deleteDirectory(TEMPLATE, directory)); + Assertions.assertFalse(storage.hasFile(TEMPLATE, directory + "/test.txt")); + } } From 154ac822b909fc20ada548ec0303fac227f08e01 Mon Sep 17 00:00:00 2001 From: 0utplay Date: Sun, 9 Jul 2023 19:20:40 +0200 Subject: [PATCH 3/3] chore: add swagger api documentation for the template api --- .../main/resources/documentation/swagger.json | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/modules/rest/src/main/resources/documentation/swagger.json b/modules/rest/src/main/resources/documentation/swagger.json index 256f044fa8..967ec0d881 100644 --- a/modules/rest/src/main/resources/documentation/swagger.json +++ b/modules/rest/src/main/resources/documentation/swagger.json @@ -3235,6 +3235,55 @@ } } }, + "/template/{storage}/{prefix}/{name}/directory" : { + "delete" : { + "parameters" : [ { + "name" : "storage", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "prefix", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "name", + "in" : "path", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "path", + "in" : "query", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "tags" : [ "Templates" ], + "summary" : "Deletes a directory and all its contents recursively from a template", + "responses" : { + "200" : { + "$ref" : "#/components/responses/Success" + }, + "400" : { + "$ref" : "#/components/responses/BadRequest" + }, + "401" : { + "$ref" : "#/components/responses/Unauthorized" + }, + "403" : { + "$ref" : "#/components/responses/Forbidden" + } + } + } + }, "/serviceversion" : { "get" : { "tags" : [ "Service Versions" ],