diff --git a/pom.xml b/pom.xml index b936cbc..69c53ef 100644 --- a/pom.xml +++ b/pom.xml @@ -16,8 +16,8 @@ https://s3ninja.net - dev-35.8.3 - dev-66.7.2 + dev-38.2.0 + dev-70.2.2 diff --git a/src/main/java/ninja/Bucket.java b/src/main/java/ninja/Bucket.java index f08804e..4c71727 100644 --- a/src/main/java/ninja/Bucket.java +++ b/src/main/java/ninja/Bucket.java @@ -136,22 +136,26 @@ public boolean exists() { *

* If the underlying directory already exists, nothing happens. * - * @return true if the folder for the bucket was created successfully and if it was missing before + * @return true if the folder for the bucket was created successfully or existed before, false else */ public boolean create() { - if (folder.exists() || !folder.mkdirs()) { + if (folder.exists()) { + return true; + } + + if (!folder.mkdirs()) { return false; } // having successfully created the folder, write the version marker writeVersion(); - return true; + return folder.isDirectory(); } /** * Deletes the bucket and all of its contents. * - * @return true if all files of the bucket and the bucket itself was deleted successfully, false otherwise. + * @return true if all files of the bucket and the bucket itself were deleted successfully, false else */ public boolean delete() { if (!folder.exists()) { @@ -161,8 +165,8 @@ public boolean delete() { try { sirius.kernel.commons.Files.delete(folder.toPath()); return true; - } catch (IOException e) { - Exceptions.handle(e); + } catch (IOException exception) { + Exceptions.handle(Storage.LOG, exception); return false; } } @@ -189,8 +193,8 @@ public void outputObjectsV1(XMLStructuredOutput output, output.property("Prefix", prefix); try { walkFileTreeOurWay(folder.toPath(), visitor); - } catch (IOException e) { - throw Exceptions.handle(e); + } catch (IOException exception) { + throw Exceptions.handle(Storage.LOG, exception); } output.property("IsTruncated", limit > 0 && visitor.getCount() > limit); output.endOutput(); @@ -218,8 +222,8 @@ public void outputObjectsV2(XMLStructuredOutput output, output.property("Prefix", prefix); try { walkFileTreeOurWay(folder.toPath(), visitor); - } catch (IOException e) { - throw Exceptions.handle(e); + } catch (IOException exception) { + throw Exceptions.handle(Storage.LOG, exception); } output.property("IsTruncated", limit > 0 && visitor.getCount() > limit); output.property("KeyCount", visitor.getCount()); @@ -239,32 +243,33 @@ private void walkFileTreeOurWay(Path path, FileVisitor visitor) th } try (Stream children = Files.list(path)) { - children.filter(p -> filterObjects(p.toFile())).sorted(Bucket::compareUtf8Binary).forEach(p -> { - try { - BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class); - visitor.visitFile(p, attrs); - } catch (IOException e) { - throw Exceptions.handle(e); - } - }); + children.filter(childPath -> filterObjects(childPath.toFile())) + .sorted(Bucket::compareUtf8Binary) + .forEach(childPath -> { + try { + visitor.visitFile(childPath, Files.readAttributes(childPath, BasicFileAttributes.class)); + } catch (IOException exception) { + throw Exceptions.handle(Storage.LOG, exception); + } + }); } } - private static int compareUtf8Binary(Path p1, Path p2) { - String s1 = StoredObject.decodeKey(p1.getFileName().toString()); - String s2 = StoredObject.decodeKey(p2.getFileName().toString()); + private static int compareUtf8Binary(Path path1, Path path2) { + String string1 = StoredObject.decodeKey(path1.getFileName().toString()); + String string2 = StoredObject.decodeKey(path2.getFileName().toString()); - byte[] b1 = s1.getBytes(StandardCharsets.UTF_8); - byte[] b2 = s2.getBytes(StandardCharsets.UTF_8); + byte[] bytes1 = string1.getBytes(StandardCharsets.UTF_8); + byte[] bytes2 = string2.getBytes(StandardCharsets.UTF_8); // unless we upgrade to java 9+ offering Arrays.compare(...), we need to compare the arrays manually :( - int length = Math.min(b1.length, b2.length); - for (int i = 0; i < length; ++i) { - if (b1[i] != b2[i]) { - return Byte.compare(b1[i], b2[i]); + int length = Math.min(bytes1.length, bytes2.length); + for (int index = 0; index < length; ++index) { + if (bytes1[index] != bytes2[index]) { + return Byte.compare(bytes1[index], bytes2[index]); } } - return b1.length - b2.length; + return bytes1.length - bytes2.length; } /** @@ -278,26 +283,35 @@ public boolean isPrivate() { /** * Marks the bucket as only privately accessible, i.e. non-public. + * + * @return true if the bucket is now only privately accessible, false else */ - public void makePrivate() { + public boolean makePrivate() { if (publicMarker.exists()) { sirius.kernel.commons.Files.delete(publicMarker); publicAccessCache.put(getName(), false); } + + return !publicMarker.exists(); } /** * Marks the bucket as publicly accessible. + * + * @return true if the bucket is now publicly accessible, false else */ - public void makePublic() { + public boolean makePublic() { if (!publicMarker.exists()) { try { new FileOutputStream(publicMarker).close(); - } catch (IOException e) { - throw Exceptions.handle(Storage.LOG, e); + } catch (IOException exception) { + Exceptions.handle(Storage.LOG, exception); + return false; } } publicAccessCache.put(getName(), true); + + return publicMarker.exists(); } /** @@ -334,15 +348,15 @@ public StoredObject getObject(String key) { */ public List getObjects(@Nullable String query, Limit limit) { try (Stream stream = Files.list(folder.toPath())) { - return stream.filter(p -> filterObjects(p.toFile())) + return stream.filter(path -> filterObjects(path.toFile())) .sorted(Bucket::compareUtf8Binary) .map(Path::toFile) .filter(currentFile -> isMatchingObject(query, currentFile)) .filter(limit.asPredicate()) .map(StoredObject::new) .toList(); - } catch (IOException e) { - throw Exceptions.handle(e); + } catch (IOException exception) { + throw Exceptions.handle(Storage.LOG, exception); } } @@ -357,8 +371,8 @@ public int countObjects(@Nullable String query) { return Math.toIntExact(stream.map(Path::toFile) .filter(currentFile -> isMatchingObject(query, currentFile)) .count()); - } catch (IOException e) { - throw Exceptions.handle(e); + } catch (IOException exception) { + throw Exceptions.handle(Storage.LOG, exception); } } @@ -392,8 +406,8 @@ private int parseVersion() { try { // parse the version from the version marker file return Integer.parseInt(Strings.join(Files.readAllLines(versionMarker.toPath()), "\n").trim()); - } catch (IOException e) { - throw Exceptions.handle(Storage.LOG, e); + } catch (IOException exception) { + throw Exceptions.handle(Storage.LOG, exception); } } @@ -410,8 +424,8 @@ protected void writeVersion() { try { // write the version into the version marker file Files.write(versionMarker.toPath(), Collections.singletonList(String.valueOf(version))); - } catch (IOException e) { - throw Exceptions.handle(Storage.LOG, e); + } catch (IOException exception) { + throw Exceptions.handle(Storage.LOG, exception); } } @@ -470,7 +484,7 @@ public static boolean isValidName(@Nullable String name) { if (IP_ADDRESS_PATTERN.matcher(name).matches() && InetAddress.getByName(name) != null) { return false; } - } catch (Exception e) { + } catch (Exception ignored) { // ignore this, we want the conversion to fail and thus to end up here } diff --git a/src/main/java/ninja/NinjaController.java b/src/main/java/ninja/NinjaController.java index 7c44100..65257ed 100644 --- a/src/main/java/ninja/NinjaController.java +++ b/src/main/java/ninja/NinjaController.java @@ -157,7 +157,12 @@ public void bucket(WebContext webContext, String bucketName) { return; } - bucket.create(); + if (!bucket.create()) { + throw Exceptions.createHandled() + .to(Storage.LOG) + .withDirectMessage("Failed creating bucket. Missing file system permission?") + .handle(); + } UserContext.message(Message.info().withTextMessage("Bucket successfully created.")); webContext.respondWith().redirectTemporarily("/ui/" + bucket.getEncodedName()); @@ -181,7 +186,12 @@ public void bucket(WebContext webContext, String bucketName) { // handle /ui/[bucket]?make-public if (webContext.hasParameter("make-public")) { - bucket.makePublic(); + if (!bucket.makePublic()) { + throw Exceptions.createHandled() + .to(Storage.LOG) + .withDirectMessage("Failed making bucket public. Missing file system permission?") + .handle(); + } UserContext.message(Message.info().withTextMessage("ACLs successfully changed")); webContext.respondWith().redirectTemporarily(address); @@ -190,7 +200,12 @@ public void bucket(WebContext webContext, String bucketName) { // handle /ui/[bucket]?make-private if (webContext.hasParameter("make-private")) { - bucket.makePrivate(); + if (!bucket.makePrivate()) { + throw Exceptions.createHandled() + .to(Storage.LOG) + .withDirectMessage("Failed making bucket private. Missing file system permission?") + .handle(); + } UserContext.message(Message.info().withTextMessage("ACLs successfully changed")); webContext.respondWith().redirectTemporarily(address); @@ -199,7 +214,12 @@ public void bucket(WebContext webContext, String bucketName) { // handle /ui/[bucket]?delete if (webContext.hasParameter("delete")) { - bucket.delete(); + if (!bucket.delete()) { + throw Exceptions.createHandled() + .to(Storage.LOG) + .withDirectMessage("Failed deleting bucket. Missing file system permission?") + .handle(); + } UserContext.message(Message.info().withTextMessage("Bucket successfully deleted.")); webContext.respondWith().redirectTemporarily("/ui"); diff --git a/src/main/java/ninja/S3Dispatcher.java b/src/main/java/ninja/S3Dispatcher.java index 67eb12d..1065f72 100644 --- a/src/main/java/ninja/S3Dispatcher.java +++ b/src/main/java/ninja/S3Dispatcher.java @@ -92,6 +92,10 @@ public class S3Dispatcher implements WebDispatcher { private static final String RESPONSE_BUCKET = "Bucket"; private static final String ERROR_MULTIPART_UPLOAD_DOES_NOT_EXIST = "Multipart Upload does not exist"; private static final String ERROR_BUCKET_DOES_NOT_EXIST = "Bucket does not exist"; + private static final String ERROR_BUCKET_IS_NOT_EMPTY = "Bucket is not empty"; + private static final String ERROR_BUCKET_ALREADY_OWNED_BY_YOU = "Bucket already owned by you"; + private static final String ERROR_FILE_SYSTEM_ACCESS = + "General problems accessing the file system — Permissions set correctly?"; private static final String PATH_DELIMITER = "/"; private static class S3Request { @@ -161,9 +165,9 @@ private static class S3Request { builder.add('.' + myself.getHostAddress()); builder.add('.' + myself.getHostName()); builder.add('.' + myself.getCanonicalHostName()); - } catch (Exception e) { + } catch (Exception exception) { // reaching this point, we failed to resolve the local host name. tant pis. - Exceptions.ignore(e); + Exceptions.ignore(exception); } DOMAINS = builder.build(); @@ -350,14 +354,14 @@ private String getAuthHash(WebContext webContext) { } String authentication = Strings.isEmpty(authorizationHeaderValue.getString()) ? "" : authorizationHeaderValue.getString(); - Matcher m = AWS_AUTH_PATTERN.matcher(authentication); - if (m.matches()) { - return m.group(2); + Matcher matcher = AWS_AUTH_PATTERN.matcher(authentication); + if (matcher.matches()) { + return matcher.group(2); } - m = AWS_AUTH4_PATTERN.matcher(authentication); - if (m.matches()) { - return m.group(7); + matcher = AWS_AUTH4_PATTERN.matcher(authentication); + if (matcher.matches()) { + return matcher.group(7); } return null; @@ -466,17 +470,47 @@ private void bucket(WebContext webContext, String bucketName) { if (!bucket.exists()) { signalObjectError(webContext, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); } else { - bucket.delete(); + if (bucket.countObjects("") > 0) { + signalObjectError(webContext, + bucketName, + null, + S3ErrorCode.BucketNotEmpty, + ERROR_BUCKET_IS_NOT_EMPTY); + return; + } + + if (!bucket.delete()) { + signalObjectError(webContext, + bucketName, + null, + S3ErrorCode.InternalError, + ERROR_FILE_SYSTEM_ACCESS); + return; + } + signalObjectSuccess(webContext); webContext.respondWith().status(HttpResponseStatus.OK); } } else if (HttpMethod.PUT.equals(method)) { - bucket.create(); + if (bucket.exists()) { + signalObjectError(webContext, + bucketName, + null, + S3ErrorCode.BucketAlreadyOwnedByYou, + ERROR_BUCKET_ALREADY_OWNED_BY_YOU); + return; + } + + if (!bucket.create()) { + signalObjectError(webContext, bucketName, null, S3ErrorCode.InternalError, ERROR_FILE_SYSTEM_ACCESS); + return; + } // in order to allow creation of public buckets, we support a single canned access control list String cannedAccessControlList = webContext.getHeader("x-amz-acl"); - if (Strings.areEqual(cannedAccessControlList, "public-read-write")) { - bucket.makePublic(); + if (Strings.areEqual(cannedAccessControlList, "public-read-write") && !bucket.makePublic()) { + signalObjectError(webContext, bucketName, null, S3ErrorCode.InternalError, ERROR_FILE_SYSTEM_ACCESS); + return; } signalObjectSuccess(webContext); @@ -573,7 +607,14 @@ private boolean checkObjectRequest(WebContext webContext, Bucket bucket, String if (!bucket.exists()) { if (storage.isAutocreateBuckets()) { - bucket.create(); + if (!bucket.create()) { + signalObjectError(webContext, + bucket.getName(), + id, + S3ErrorCode.InternalError, + ERROR_FILE_SYSTEM_ACCESS); + return false; + } } else { signalObjectError(webContext, bucket.getName(), @@ -903,13 +944,14 @@ private void startMultipartUpload(WebContext webContext, Bucket bucket, String i } private void storePropertiesInUploadDir(Map properties, String uploadId) { - Properties props = new Properties(); - properties.forEach(props::setProperty); - try (FileOutputStream propsOut = new FileOutputStream(new File(getUploadDir(uploadId), - TEMPORARY_PROPERTIES_FILENAME))) { - props.store(propsOut, ""); - } catch (IOException e) { - Exceptions.handle(e); + Properties clonedProperties = new Properties(); + properties.forEach(clonedProperties::setProperty); + + try (FileOutputStream outputStream = new FileOutputStream(new File(getUploadDir(uploadId), + TEMPORARY_PROPERTIES_FILENAME))) { + clonedProperties.store(outputStream, ""); + } catch (IOException exception) { + Exceptions.handle(exception); } } @@ -946,12 +988,12 @@ private void multiObject(WebContext webContext, String uploadId, String partNumb .setHeader(HTTP_HEADER_NAME_ETAG, etag) .addHeader(HttpHeaderNames.ACCESS_CONTROL_EXPOSE_HEADERS, HTTP_HEADER_NAME_ETAG) .status(HttpResponseStatus.OK); - } catch (IOException e) { + } catch (IOException exception) { errorSynthesizer.synthesiseError(webContext, null, null, S3ErrorCode.InternalError, - Exceptions.handle(e).getMessage()); + Exceptions.handle(exception).getMessage()); } } @@ -987,8 +1029,8 @@ private void completeMultipartUpload(WebContext webContext, }); try { reader.parse(in); - } catch (IOException e) { - Exceptions.handle(e); + } catch (IOException exception) { + Exceptions.handle(exception); } File file = combineParts(id, @@ -1030,8 +1072,8 @@ private void completeMultipartUpload(WebContext webContext, out.property("Key", id); out.property(HTTP_HEADER_NAME_ETAG, etag); out.endOutput(); - } catch (IOException e) { - Exceptions.ignore(e); + } catch (IOException exception) { + Exceptions.ignore(exception); errorSynthesizer.synthesiseError(webContext, null, null, @@ -1041,9 +1083,9 @@ private void completeMultipartUpload(WebContext webContext, } private void commitPropertiesFromUploadDir(String uploadId, StoredObject object) throws IOException { - File propsFile = new File(getUploadDir(uploadId), TEMPORARY_PROPERTIES_FILENAME); - if (propsFile.exists()) { - Files.move(propsFile, object.getPropertiesFile()); + File propertiesFile = new File(getUploadDir(uploadId), TEMPORARY_PROPERTIES_FILENAME); + if (propertiesFile.exists()) { + Files.move(propertiesFile, object.getPropertiesFile()); } } @@ -1060,11 +1102,12 @@ private File combineParts(String id, String uploadId, List parts) { file.getName(), file.getAbsolutePath()); } - try (FileChannel out = new FileOutputStream(file).getChannel()) { - combine(parts, out); + try (FileOutputStream outStream = new FileOutputStream(file); + FileChannel outChannel = outStream.getChannel()) { + combine(parts, outChannel); } - } catch (IOException e) { - throw Exceptions.handle(e); + } catch (IOException exception) { + throw Exceptions.handle(exception); } return file; @@ -1072,9 +1115,9 @@ private File combineParts(String id, String uploadId, List parts) { private void combine(List parts, FileChannel out) throws IOException { for (File part : parts) { - try (RandomAccessFile raf = new RandomAccessFile(part, "r")) { - FileChannel channel = raf.getChannel(); - out.write(channel.map(FileChannel.MapMode.READ_ONLY, 0, raf.length())); + try (RandomAccessFile randomAccessToPart = new RandomAccessFile(part, "r")) { + out.write(randomAccessToPart.getChannel() + .map(FileChannel.MapMode.READ_ONLY, 0, randomAccessToPart.length())); } } } @@ -1094,8 +1137,8 @@ private void abortMultipartUpload(WebContext webContext, String uploadId) { private static void delete(File file) { try { sirius.kernel.commons.Files.delete(file.toPath()); - } catch (IOException e) { - Exceptions.handle(Storage.LOG, e); + } catch (IOException exception) { + Exceptions.handle(Storage.LOG, exception); } } diff --git a/src/main/java/ninja/errors/S3ErrorCode.java b/src/main/java/ninja/errors/S3ErrorCode.java index a9006a3..095c275 100644 --- a/src/main/java/ninja/errors/S3ErrorCode.java +++ b/src/main/java/ninja/errors/S3ErrorCode.java @@ -18,12 +18,80 @@ @SuppressWarnings("java:S115") @Explain("We use the proper names as defined in the AWS API") public enum S3ErrorCode { - AccessDenied(HttpResponseStatus.FORBIDDEN), BadDigest(HttpResponseStatus.BAD_REQUEST), - IncompleteBody(HttpResponseStatus.BAD_REQUEST), InternalError(HttpResponseStatus.INTERNAL_SERVER_ERROR), - InvalidDigest(HttpResponseStatus.BAD_REQUEST), InvalidRequest(HttpResponseStatus.BAD_REQUEST), - NoSuchBucket(HttpResponseStatus.NOT_FOUND), NoSuchBucketPolicy(HttpResponseStatus.NOT_FOUND), - NoSuchKey(HttpResponseStatus.NOT_FOUND), NoSuchLifecycleConfiguration(HttpResponseStatus.NOT_FOUND), - NoSuchUpload(HttpResponseStatus.NOT_FOUND), SignatureDoesNotMatch(HttpResponseStatus.FORBIDDEN); + /** + * Access denied. + */ + AccessDenied(HttpResponseStatus.FORBIDDEN), + + /** + * During upload, the specified checksum value did not match the calculated one. + */ + BadDigest(HttpResponseStatus.BAD_REQUEST), + + /** + * The specified bucket name is already in use by somebody else. + */ + BucketAlreadyExists(HttpResponseStatus.CONFLICT), + + /** + * The specified bucket name is already in use by yourself. + */ + BucketAlreadyOwnedByYou(HttpResponseStatus.CONFLICT), + + /** + * The specified bucket can not be deleted as it is not empty. + */ + BucketNotEmpty(HttpResponseStatus.CONFLICT), + + /** + * During upload, less than the number of bytes specified have been transmitted. + */ + IncompleteBody(HttpResponseStatus.BAD_REQUEST), + + /** + * An internal error has occurred. + */ + InternalError(HttpResponseStatus.INTERNAL_SERVER_ERROR), + + /** + * The specified checksum value is invalid. + */ + InvalidDigest(HttpResponseStatus.BAD_REQUEST), + + /** + * The current request is not valid. + */ + InvalidRequest(HttpResponseStatus.BAD_REQUEST), + + /** + * The specified bucket does not exist. + */ + NoSuchBucket(HttpResponseStatus.NOT_FOUND), + + /** + * The specified bucket does not have a policy. + */ + NoSuchBucketPolicy(HttpResponseStatus.NOT_FOUND), + + /** + * The specified key does not exist. + */ + NoSuchKey(HttpResponseStatus.NOT_FOUND), + + /** + * The specified lifecycle configuration does not exist. + */ + NoSuchLifecycleConfiguration(HttpResponseStatus.NOT_FOUND), + + /** + * The specified multipart upload does not exist. + */ + NoSuchUpload(HttpResponseStatus.NOT_FOUND), + + /** + * The provided request signature does not match the one calculated by the server. + */ + SignatureDoesNotMatch(HttpResponseStatus.FORBIDDEN); private final HttpResponseStatus httpStatusCode; diff --git a/src/test/java/BaseAWSSpec.groovy b/src/test/java/BaseAWSSpec.groovy index 8fb7bd2..44d4387 100644 --- a/src/test/java/BaseAWSSpec.groovy +++ b/src/test/java/BaseAWSSpec.groovy @@ -44,15 +44,26 @@ abstract class BaseAWSSpec extends BaseSpecification { client.putObject(bucketName, key, new ByteArrayInputStream(data), metadata) } + /** + * Before each test, delete all buckets and their objects. This allows to run the test based on a clean state. + */ + def setup() { + def client = getClient() + + client.listBuckets().stream().forEach { + def bucket = it + client.listObjects(bucket.getName()).getObjectSummaries().stream().forEach { + client.deleteObject(bucket.getName(), it.getKey()) + } + client.deleteBucket(bucket.getName()) + } + } + def "HEAD of non-existing bucket as expected"() { given: def bucketName = "does-not-exist" def client = getClient() - when: - if (client.doesBucketExist(bucketName)) { - client.deleteBucket(bucketName) - } - then: + expect: !client.doesBucketExist(bucketName) !client.doesBucketExistV2(bucketName) } @@ -62,13 +73,12 @@ abstract class BaseAWSSpec extends BaseSpecification { def bucketName = DEFAULT_BUCKET_NAME def client = getClient() when: - if (client.doesBucketExist(bucketName)) { - client.deleteBucket(bucketName) - } client.createBucket(bucketName) then: client.doesBucketExist(bucketName) client.doesBucketExistV2(bucketName) + cleanup: + client.deleteBucket(bucketName) } def "DELETE of non-existing bucket as expected"() { @@ -76,14 +86,11 @@ abstract class BaseAWSSpec extends BaseSpecification { def bucketName = "does-not-exist" def client = getClient() when: - if (client.doesBucketExist(bucketName)) { - client.deleteBucket(bucketName) - } - and: client.deleteBucket(bucketName) then: AmazonS3Exception e = thrown() e.statusCode == 404 + and: !client.doesBucketExist(bucketName) !client.doesBucketExistV2(bucketName) } @@ -93,11 +100,12 @@ abstract class BaseAWSSpec extends BaseSpecification { def bucketName = DEFAULT_BUCKET_NAME def client = getClient() when: - if (!client.doesBucketExist(bucketName)) { - client.createBucket(bucketName) - } - client.deleteBucket(bucketName) + client.createBucket(bucketName) + and: + client.doesBucketExist(bucketName) then: + client.deleteBucket(bucketName) + and: !client.doesBucketExist(bucketName) } @@ -107,9 +115,7 @@ abstract class BaseAWSSpec extends BaseSpecification { def key = DEFAULT_KEY def client = getClient() when: - if (!client.doesBucketExist(bucketName)) { - client.createBucket(bucketName) - } + client.createBucket(bucketName) and: File file = File.createTempFile("test", "") file.delete() @@ -125,8 +131,9 @@ abstract class BaseAWSSpec extends BaseSpecification { tm.download(bucketName, key, download).waitForCompletion() then: Files.toString(file, StandardCharsets.UTF_8) == Files.toString(download, StandardCharsets.UTF_8) - and: + cleanup: client.deleteObject(bucketName, key) + client.deleteBucket(bucketName) } def "PUT and then GET work as expected"() { @@ -135,9 +142,7 @@ abstract class BaseAWSSpec extends BaseSpecification { def key = DEFAULT_KEY def client = getClient() when: - if (!client.doesBucketExist(bucketName)) { - client.createBucket(bucketName) - } + client.createBucket(bucketName) and: putObjectWithContent(bucketName, key, "Test") def content = new String( @@ -152,8 +157,9 @@ abstract class BaseAWSSpec extends BaseSpecification { content == "Test" and: downloadedData == "Test" - and: + cleanup: client.deleteObject(bucketName, key) + client.deleteBucket(bucketName) } def "PUT and then LIST work as expected"() { @@ -163,9 +169,6 @@ abstract class BaseAWSSpec extends BaseSpecification { def key2 = DEFAULT_KEY + "/Zwei" def client = getClient() when: - if (client.doesBucketExist(bucketName)) { - client.deleteBucket(bucketName) - } client.createBucket(bucketName) and: putObjectWithContent(bucketName, key1, "Eins") @@ -176,6 +179,10 @@ abstract class BaseAWSSpec extends BaseSpecification { summaries.size() == 2 summaries.get(0).getKey() == key1 summaries.get(1).getKey() == key2 + cleanup: + client.deleteObject(bucketName, key1) + client.deleteObject(bucketName, key2) + client.deleteBucket(bucketName) } // reported in https://github.com/scireum/s3ninja/issues/180 @@ -187,9 +194,6 @@ abstract class BaseAWSSpec extends BaseSpecification { def key3 = "a/key/with a different/prefix/Drei" def client = getClient() when: - if (client.doesBucketExist(bucketName)) { - client.deleteBucket(bucketName) - } client.createBucket(bucketName) and: putObjectWithContent(bucketName, key1, "Eins") @@ -201,6 +205,11 @@ abstract class BaseAWSSpec extends BaseSpecification { summaries.size() == 2 summaries.get(0).getKey() == key1 summaries.get(1).getKey() == key2 + cleanup: + client.deleteObject(bucketName, key1) + client.deleteObject(bucketName, key2) + client.deleteObject(bucketName, key3) + client.deleteBucket(bucketName) } def "PUT and then DELETE work as expected"() { @@ -209,16 +218,16 @@ abstract class BaseAWSSpec extends BaseSpecification { def key = DEFAULT_KEY def client = getClient() when: - if (!client.doesBucketExist(bucketName)) { - client.createBucket(bucketName) - } + client.createBucket(bucketName) and: putObjectWithContent(bucketName, key, "Test") - client.deleteBucket(bucketName) + client.deleteObject(bucketName, key) client.getObject(bucketName, key) then: AmazonS3Exception e = thrown() e.statusCode == 404 + cleanup: + client.deleteBucket(bucketName) } def "MultipartUpload and then GET work as expected"() { @@ -226,17 +235,15 @@ abstract class BaseAWSSpec extends BaseSpecification { def bucketName = DEFAULT_BUCKET_NAME def key = DEFAULT_KEY def client = getClient() - when: + and: def transfer = TransferManagerBuilder.standard(). withS3Client(client). withMultipartUploadThreshold(1). withMinimumUploadPartSize(1).build() def meta = new ObjectMetadata() def message = "Test".getBytes(StandardCharsets.UTF_8) - and: - if (!client.doesBucketExist(bucketName)) { - client.createBucket(bucketName) - } + when: + client.createBucket(bucketName) and: meta.setContentLength(message.length) meta.addUserMetadata("userdata", "test123") @@ -249,8 +256,9 @@ abstract class BaseAWSSpec extends BaseSpecification { then: content == "Test" userdata == "test123" - and: + cleanup: client.deleteObject(bucketName, key) + client.deleteBucket(bucketName) } def "MultipartUpload and then DELETE work as expected"() { @@ -266,18 +274,18 @@ abstract class BaseAWSSpec extends BaseSpecification { def meta = new ObjectMetadata() def message = "Test".getBytes(StandardCharsets.UTF_8) and: - if (!client.doesBucketExist(bucketName)) { - client.createBucket(bucketName) - } + client.createBucket(bucketName) and: meta.setContentLength(message.length) def upload = transfer.upload(bucketName, key, new ByteArrayInputStream(message), meta) upload.waitForUploadResult() - client.deleteBucket(bucketName) + client.deleteObject(bucketName, key) client.getObject(bucketName, key) then: AmazonS3Exception e = thrown() e.statusCode == 404 + cleanup: + client.deleteBucket(bucketName) } def "PUT on presigned URL without signed chunks works as expected"() { @@ -286,9 +294,7 @@ abstract class BaseAWSSpec extends BaseSpecification { def key = DEFAULT_KEY def client = getClient() when: - if (!client.doesBucketExist(bucketName)) { - client.createBucket(bucketName) - } + client.createBucket(bucketName) and: def content = "NotSigned" and: @@ -310,8 +316,9 @@ abstract class BaseAWSSpec extends BaseSpecification { String downloadedData = new String(ByteStreams.toByteArray(c.getInputStream()), StandardCharsets.UTF_8) then: downloadedData == content - and: + cleanup: client.deleteObject(bucketName, key) + client.deleteBucket(bucketName) } // reported in https://github.com/scireum/s3ninja/issues/153 @@ -321,9 +328,7 @@ abstract class BaseAWSSpec extends BaseSpecification { def key = DEFAULT_KEY def client = getClient() when: - if (!client.doesBucketExist(bucketName)) { - client.createBucket(bucketName) - } + client.createBucket(bucketName) and: putObjectWithContent(bucketName, key, "Test") def content = new String( @@ -342,8 +347,9 @@ abstract class BaseAWSSpec extends BaseSpecification { content == "Test" and: downloadedData == "Test" - and: + cleanup: client.deleteObject(bucketName, key) + client.deleteBucket(bucketName) } // reported in https://github.com/scireum/s3ninja/issues/181 @@ -355,6 +361,8 @@ abstract class BaseAWSSpec extends BaseSpecification { def key3 = DEFAULT_KEY + "/Drei" def client = getClient() when: + client.createBucket(bucketName) + and: putObjectWithContent(bucketName, key1, "Eins") putObjectWithContent(bucketName, key2, "Zwei") putObjectWithContent(bucketName, key3, "Drei") @@ -367,8 +375,9 @@ abstract class BaseAWSSpec extends BaseSpecification { def listing = client.listObjects(bucketName) listing.getObjectSummaries().size() == 1 listing.getObjectSummaries().get(0).getKey() == key3 - and: + cleanup: client.deleteObject(bucketName, key3) + client.deleteBucket(bucketName) } // reported in https://github.com/scireum/s3ninja/issues/214 @@ -380,6 +389,8 @@ abstract class BaseAWSSpec extends BaseSpecification { def key3 = DEFAULT_KEY + "/Drei" def client = getClient() when: + client.createBucket(bucketName) + and: putObjectWithContent(bucketName, key1, "Eins") putObjectWithContent(bucketName, key2, "Zwei") putObjectWithContent(bucketName, key3, "Drei") @@ -389,10 +400,11 @@ abstract class BaseAWSSpec extends BaseSpecification { result.getObjectSummaries().size() == 2 result.getObjectSummaries().get(0).getKey() == key1 result.getObjectSummaries().get(1).getKey() == key2 - and: + cleanup: client.deleteObject(bucketName, key1) client.deleteObject(bucketName, key2) client.deleteObject(bucketName, key3) + client.deleteBucket(bucketName) } // reported in https://github.com/scireum/s3ninja/issues/209 @@ -412,7 +424,7 @@ abstract class BaseAWSSpec extends BaseSpecification { connection.getResponseCode() == 200 connection.getContentLengthLong() == content.getBytes(StandardCharsets.UTF_8).length connection.disconnect() - and: + cleanup: client.deleteObject(bucketName, key) client.deleteBucket(bucketName) } @@ -426,9 +438,7 @@ abstract class BaseAWSSpec extends BaseSpecification { def content = "I am pointless text content, but I deserve to exist twice and will thus be copied!" def client = getClient() when: - if (!client.doesBucketExist(bucketName)) { - client.createBucket(bucketName) - } + client.createBucket(bucketName) and: putObjectWithContent(bucketName, keyFrom, content) and: @@ -440,7 +450,7 @@ abstract class BaseAWSSpec extends BaseSpecification { String downloadedData = new String(ByteStreams.toByteArray(c.getInputStream()), StandardCharsets.UTF_8) then: downloadedData == content - and: + cleanup: client.deleteObject(bucketName, keyFrom) client.deleteObject(bucketName, keyTo) client.deleteBucket(bucketName) @@ -455,13 +465,9 @@ abstract class BaseAWSSpec extends BaseSpecification { def content = "I am pointless text content, but I deserve to exist twice and will thus be copied!" def client = getClient() when: - if (!client.doesBucketExist(bucketNameFrom)) { - client.createBucket(bucketNameFrom) - } + client.createBucket(bucketNameFrom) and: - if (!client.doesBucketExist(bucketNameTo)) { - client.createBucket(bucketNameTo) - } + client.createBucket(bucketNameTo) and: putObjectWithContent(bucketNameFrom, key, content) and: @@ -473,7 +479,7 @@ abstract class BaseAWSSpec extends BaseSpecification { String downloadedData = new String(ByteStreams.toByteArray(c.getInputStream()), StandardCharsets.UTF_8) then: downloadedData == content - and: + cleanup: client.deleteObject(bucketNameFrom, key) client.deleteBucket(bucketNameFrom) client.deleteObject(bucketNameTo, key)