Skip to content

Commit

Permalink
fix(core): now creating each sub-directory in the path recursively up…
Browse files Browse the repository at this point in the history
…on creating a resource or directory for listing to work properly
  • Loading branch information
brian-mulier-p committed Nov 8, 2023
1 parent 45cd541 commit ae94ca0
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 15 deletions.
41 changes: 26 additions & 15 deletions src/main/java/io/kestra/storage/s3/S3Storage.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;

Expand Down Expand Up @@ -82,7 +79,9 @@ private InputStream get(String path) throws IOException {
@Override
public List<FileAttributes> list(String tenantId, URI uri) throws IOException {
String path = getPath(tenantId, uri);
String prefix = (path.endsWith("/")) ? path : path + "/";
String prefix = path.isEmpty()
? path
: (path.endsWith("/") ? path : path + "/");
try {
ListObjectsV2Request request = ListObjectsV2Request.builder()
.bucket(s3Config.getBucket())
Expand Down Expand Up @@ -161,7 +160,8 @@ private FileAttributes getFileAttributes(String path) throws IOException {
.key(path)
.build();
S3FileAttributes.S3FileAttributesBuilder builder = S3FileAttributes.builder()
.fileName(Path.of(path).getFileName().toString())
.fileName(Optional.ofNullable(Path.of(path).getFileName()).map(Path::toString)
.orElse("/"))
.head(s3Client.headObject(headObjectRequest));
if (path.endsWith("/")) {
builder.isDirectory(true);
Expand Down Expand Up @@ -231,15 +231,21 @@ public URI createDirectory(String tenantId, URI uri) throws IOException {

private void mkdirs(String path) throws IOException {
try {
if (!path.endsWith("/") && path.lastIndexOf('/') > 0) {
path = path.substring(0, path.lastIndexOf('/') + 1);
if(!path.startsWith("/")) {
path = "/" + path;
}

PutObjectRequest putRequest = PutObjectRequest.builder()
.bucket(s3Config.getBucket())
.key(path)
.build();
s3Client.putObject(putRequest, RequestBody.empty());
// perform 1 put request per parent directory in the path
String[] directories = path.split("/");
StringBuilder aggregatedPath = new StringBuilder();
for (int i = 0; i < directories.length - (path.endsWith("/") ? 1 : 2); i++) {
aggregatedPath.append(directories[i]).append("/");
PutObjectRequest putRequest = PutObjectRequest.builder()
.bucket(s3Config.getBucket())
.key(aggregatedPath.toString())
.build();
s3Client.putObject(putRequest, RequestBody.empty());
}
} catch (AwsServiceException | SdkClientException exception) {
throw new IOException(exception);
}
Expand Down Expand Up @@ -332,11 +338,16 @@ public List<URI> deleteByPrefix(String tenantId, URI storagePrefix) throws IOExc
}

private String getPath(String tenantId, URI uri) {
if(uri == null) {
return "";
}

parentTraversalGuard(uri);
String path = uri.getPath();
if (tenantId == null) {
return uri.getPath();
return path;
}
return "/" + tenantId + uri.getPath();
return "/" + tenantId + (path.startsWith("/") ? "" : "/") + path;
}

// Traversal does not work with s3 but it just return empty objects so throwing is more explicit
Expand Down
17 changes: 17 additions & 0 deletions src/test/java/io/kestra/storage/s3/S3StorageTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.kestra.storage.s3;

import io.kestra.core.storage.StorageTestSuite;
import io.kestra.core.storages.FileAttributes;
import io.kestra.core.storages.StorageInterface;
import io.kestra.core.utils.IdUtils;
import jakarta.inject.Inject;
Expand All @@ -14,6 +15,7 @@

import java.io.*;
import java.net.URI;
import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
Expand Down Expand Up @@ -61,4 +63,19 @@ void getEmptyFile() throws IOException {
assertThat(inputStream, not(instanceOf(ResponseInputStream.class)));
assertThat(new BufferedReader(new InputStreamReader(inputStream)).lines().count(), is(0L));
}

@Test
void mkdirRecursive() throws IOException {
storageInterface.createDirectory(null, URI.create("/first/second/third"));

List<FileAttributes> list = storageInterface.list(null, null);
assertThat(list, contains(
hasProperty("fileName", is("/"))
));

list = storageInterface.list(null, URI.create("/"));
assertThat(list, contains(
hasProperty("fileName", is("first"))
));
}
}

0 comments on commit ae94ca0

Please sign in to comment.