Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions src/main/scala/io/findify/s3mock/provider/FileProvider.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package io.findify.s3mock.provider
import java.io.{FileInputStream, File ⇒ JFile}
import java.util.UUID
import java.io.{FileInputStream, File => JFile}

import akka.http.scaladsl.model.DateTime
import better.files.File
import better.files.File.OpenOptions
import com.amazonaws.services.s3.model.ObjectMetadata
import com.amazonaws.services.s3.model.{ObjectMetadata, Tag}
import com.typesafe.scalalogging.LazyLogging
import io.findify.s3mock.error.{NoSuchBucketException, NoSuchKeyException}
import io.findify.s3mock.provider.metadata.{MapMetadataStore, MetadataStore}
import io.findify.s3mock.provider.tags.InMemoryTagStore
import io.findify.s3mock.request.{CompleteMultipartUpload, CreateBucketConfiguration}
import io.findify.s3mock.response._
import org.apache.commons.codec.digest.DigestUtils
Expand All @@ -23,6 +24,7 @@ class FileProvider(dir:String) extends Provider with LazyLogging {
if (!workDir.exists) workDir.createDirectories()

private val meta = new MapMetadataStore(dir)
private val tagStore = new InMemoryTagStore

override def metadataStore: MetadataStore = meta

Expand Down Expand Up @@ -166,6 +168,36 @@ class FileProvider(dir:String) extends Provider with LazyLogging {
metadataStore.remove(bucket)
}

override def deleteObjectTagging(bucket:String, key:String): Unit = {
val bucketFile = File(s"$dir/$bucket")
val file = File(s"$dir/$bucket/$key")
logger.debug(s"reading object for s://$bucket/$key")
if (!bucketFile.exists) throw NoSuchBucketException(bucket)
if (!file.exists) throw NoSuchKeyException(bucket, key)
if (file.isDirectory) throw NoSuchKeyException(bucket, key)
tagStore.delete(bucket, key)
}

override def getObjectTagging(bucket:String, key:String): GetObjectTagging = {
val bucketFile = File(s"$dir/$bucket")
val file = File(s"$dir/$bucket/$key")
logger.debug(s"reading object for s://$bucket/$key")
if (!bucketFile.exists) throw NoSuchBucketException(bucket)
if (!file.exists) throw NoSuchKeyException(bucket, key)
if (file.isDirectory) throw NoSuchKeyException(bucket, key)
GetObjectTagging(tagStore.get(bucket, key).getOrElse(List.empty))
}

override def setObjectTagging(bucket:String, key:String, tags: List[Tag]): Unit = {
val bucketFile = File(s"$dir/$bucket")
val file = File(s"$dir/$bucket/$key")
logger.debug(s"reading object for s://$bucket/$key")
if (!bucketFile.exists) throw NoSuchBucketException(bucket)
if (!file.exists) throw NoSuchKeyException(bucket, key)
if (file.isDirectory) throw NoSuchKeyException(bucket, key)
tagStore.set(bucket, key, tags)
}

/** Replace the os separator with a '/' */
private def fromOs(path: String): String = {
path.replace(JFile.separatorChar, '/')
Expand Down
42 changes: 41 additions & 1 deletion src/main/scala/io/findify/s3mock/provider/InMemoryProvider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import java.time.Instant
import java.util.{Date, UUID}

import akka.http.scaladsl.model.DateTime
import com.amazonaws.services.s3.model.ObjectMetadata
import com.amazonaws.services.s3.model.{ObjectMetadata, Tag}
import com.typesafe.scalalogging.LazyLogging
import io.findify.s3mock.error.{NoSuchBucketException, NoSuchKeyException}
import io.findify.s3mock.provider.metadata.{InMemoryMetadataStore, MetadataStore}
import io.findify.s3mock.provider.tags.InMemoryTagStore
import io.findify.s3mock.request.{CompleteMultipartUpload, CreateBucketConfiguration}
import io.findify.s3mock.response._
import org.apache.commons.codec.digest.DigestUtils
Expand All @@ -17,9 +18,11 @@ import scala.collection.mutable
import scala.util.Random

class InMemoryProvider extends Provider with LazyLogging {

private val mdStore = new InMemoryMetadataStore
private val bucketDataStore = new TrieMap[String, BucketContents]
private val multipartTempStore = new TrieMap[String, mutable.SortedSet[MultipartChunk]]
private val tagStore = new InMemoryTagStore

private case class BucketContents(creationTime: DateTime, keysInBucket: mutable.Map[String, KeyContents])

Expand Down Expand Up @@ -186,4 +189,41 @@ class InMemoryProvider extends Provider with LazyLogging {
case None => throw NoSuchBucketException(bucket)
}
}

override def deleteObjectTagging(bucket: String, key: String): Unit = {
bucketDataStore.get(bucket) match {
case Some(bucketContent) => bucketContent.keysInBucket.get(key) match {
case Some(keyContent) =>
logger.debug(s"removing tags for s://$bucket/$key")
tagStore.delete(bucket, key)
case None => throw NoSuchKeyException(bucket, key)
}
case None => throw NoSuchBucketException(bucket)
}
}

override def getObjectTagging(bucket: String, key: String): GetObjectTagging = {
bucketDataStore.get(bucket) match {
case Some(bucketContent) => bucketContent.keysInBucket.get(key) match {
case Some(keyContent) =>
logger.debug(s"reading tags for s://$bucket/$key")
GetObjectTagging(tagStore.get(bucket, key).getOrElse(List.empty))
case None => throw NoSuchKeyException(bucket, key)
}
case None => throw NoSuchBucketException(bucket)
}
}

override def setObjectTagging(bucket: String, key: String, tags: List[Tag]): Unit = {
bucketDataStore.get(bucket) match {
case Some(bucketContent) => bucketContent.keysInBucket.get(key) match {
case Some(keyContent) =>
logger.debug(s"setting tags for s://$bucket/$key")
tagStore.set(bucket, key, tags)
case None => throw NoSuchKeyException(bucket, key)
}
case None => throw NoSuchBucketException(bucket)
}
}

}
7 changes: 6 additions & 1 deletion src/main/scala/io/findify/s3mock/provider/Provider.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.findify.s3mock.provider

import com.amazonaws.services.s3.model.ObjectMetadata
import com.amazonaws.services.s3.model.{CompleteMultipartUploadResult ⇒ _, CopyObjectResult ⇒ _, InitiateMultipartUploadResult ⇒ _, _}
import io.findify.s3mock.provider.metadata.MetadataStore
import io.findify.s3mock.request.{CompleteMultipartUpload, CreateBucketConfiguration}
import io.findify.s3mock.response._
Expand All @@ -25,6 +25,11 @@ trait Provider {
def deleteBucket(bucket:String):Unit
def copyObject(sourceBucket: String, sourceKey: String, destBucket: String, destKey: String, newMeta: Option[ObjectMetadata] = None): CopyObjectResult
def copyObjectMultipart(sourceBucket: String, sourceKey: String, destBucket: String, destKey: String, partNumber:Int, uploadId:String, fromByte: Int, toByte:Int, meta: Option[ObjectMetadata] = None): CopyObjectResult

def deleteObjectTagging(bucket:String, key:String): Unit
def getObjectTagging(bucket:String, key:String): GetObjectTagging
def setObjectTagging(bucket:String, key:String, tags: List[Tag]): Unit

}


Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.findify.s3mock.provider.tags

import com.amazonaws.services.s3.model.{ObjectMetadata, Tag}

import scala.collection.concurrent.TrieMap
import scala.collection.mutable

class InMemoryTagStore extends TagStore {

private val bucketTags = new TrieMap[String, mutable.Map[String, List[Tag]]]

override def delete(bucket: String, key: String): Unit = {
val currentTags = bucketTags.get(bucket)
currentTags.flatMap(_.remove(key))
}

override def get(bucket: String, key: String): Option[List[Tag]] =
bucketTags.get(bucket).flatMap(_.get(key))

override def set(bucket: String, key: String, tags: List[Tag]): Unit = {
val currentTags = bucketTags.getOrElseUpdate(bucket, new TrieMap[String, List[Tag]]())
currentTags.put(key, tags)
}

override def remove(bucket: String): Unit = bucketTags.remove(bucket)

}
10 changes: 10 additions & 0 deletions src/main/scala/io/findify/s3mock/provider/tags/TagStore.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.findify.s3mock.provider.tags

import com.amazonaws.services.s3.model.Tag

trait TagStore {
def delete(bucket: String, key: String): Unit
def get(bucket: String, key: String): Option[List[Tag]]
def remove(bucket: String): Unit
def set(bucket: String, key: String, tags: List[Tag]): Unit
}
20 changes: 20 additions & 0 deletions src/main/scala/io/findify/s3mock/response/GetObjectTagging.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.findify.s3mock.response

import com.amazonaws.services.s3.model.Tag

case class GetObjectTagging(tags: List[Tag]) {
def toXML =
<Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<TagSet>
{
tags.map(tag =>
<Tag>
<Key>{tag.getKey}</Key>
<Value>{tag.getValue}</Value>
</Tag>
)
}
</TagSet>
</Tagging>
}

36 changes: 23 additions & 13 deletions src/main/scala/io/findify/s3mock/route/DeleteObject.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,29 @@ import scala.util.{Failure, Success, Try}
*/
case class DeleteObject(implicit provider: Provider) extends LazyLogging {
def route(bucket:String, path:String) = delete {
complete {
Try(provider.deleteObject(bucket, path)) match {
case Success(_) =>
logger.info(s"deleted object $bucket/$path")
HttpResponse(StatusCodes.NoContent)
case Failure(NoSuchKeyException(_, _)) =>
logger.info(s"cannot delete object $bucket/$path: no such key")
HttpResponse(StatusCodes.NotFound)
case Failure(ex) =>
logger.error(s"cannot delete object $bucket/$path", ex)
HttpResponse(StatusCodes.NotFound)
parameter('tagging?) { (tagging) ⇒
tagging match {
case Some(_) ⇒
complete {
handleTry(bucket, path, Try(provider.deleteObjectTagging(bucket, path)))
}
case None ⇒
complete {
handleTry(bucket, path, Try(provider.deleteObject(bucket, path)))
}
}

}
}
}

private def handleTry[A](bucket: String, path: String, t: Try[A]) = t match {
case Success(_) =>
logger.info(s"deleted object $bucket/$path")
HttpResponse(StatusCodes.NoContent)
case Failure(NoSuchKeyException(_, _)) =>
logger.info(s"cannot delete object $bucket/$path: no such key")
HttpResponse(StatusCodes.NotFound)
case Failure(ex) =>
logger.error(s"cannot delete object $bucket/$path", ex)
HttpResponse(StatusCodes.NotFound)
}
}
61 changes: 32 additions & 29 deletions src/main/scala/io/findify/s3mock/route/GetObject.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.amazonaws.util.DateUtils
import com.typesafe.scalalogging.LazyLogging
import io.findify.s3mock.error.{InternalErrorException, NoSuchBucketException, NoSuchKeyException}
import io.findify.s3mock.provider.{GetObjectData, Provider}
import io.findify.s3mock.response.GetObjectTagging

import scala.collection.JavaConverters._
import scala.util.{Failure, Success, Try}
Expand All @@ -38,7 +39,7 @@ case class GetObject(implicit provider: Provider) extends LazyLogging {
}

if (params.contains("tagging")) {
handleTaggingRequest(meta)
handleTaggingRequest(provider.getObjectTagging(bucket, path), meta)
} else {
HttpResponse(
status = StatusCodes.OK,
Expand Down Expand Up @@ -77,44 +78,46 @@ case class GetObject(implicit provider: Provider) extends LazyLogging {



protected def handleTaggingRequest(meta: ObjectMetadata): HttpResponse = {
protected def handleTaggingRequest(tagsMeta: GetObjectTagging, meta: ObjectMetadata): HttpResponse = {
var root = <Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/"></Tagging>
var tagset = <TagSet></TagSet>

var w = new StringWriter()

if (meta.getRawMetadata.containsKey("x-amz-tagging")){
var doc =
<Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<TagSet>
{
meta.getRawMetadata.get("x-amz-tagging").asInstanceOf[String].split("&").map(
(rawTag: String) => {
rawTag.split("=", 2).map(
(part: String) => URLDecoder.decode(part, "UTF-8")
)
}).map(
(kv: Array[String]) =>
<Tag>
<Key>{kv(0)}</Key>
<Value>{kv(1)}</Value>
</Tag>)
var doc =
<Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<TagSet>
{
tagsMeta.tags.map(tag =>
<Tag>
<Key>{tag.getKey}</Key>
<Value>{tag.getValue}</Value>
</Tag>
)
}
{
if (meta.getRawMetadata.containsKey("x-amz-tagging")){
meta.getRawMetadata.get("x-amz-tagging").asInstanceOf[String].split("&").map(
(rawTag: String) => {
rawTag.split("=", 2).map(
(part: String) => URLDecoder.decode(part, "UTF-8")
)
}).map(
(kv: Array[String]) =>
<Tag>
<Key>{kv(0)}</Key>
<Value>{kv(1)}</Value>
</Tag>)
}
</TagSet>
</Tagging>
}
</TagSet>
</Tagging>

xml.XML.write(w, doc, "UTF-8", true, null)

xml.XML.write(w, doc, "UTF-8", true, null)
} else {
var doc = <Tagging xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><TagSet></TagSet></Tagging>
xml.XML.write(w, doc, "UTF-8", true, null)
}

meta.setContentType("application/xml; charset=utf-8")
HttpResponse(
status = StatusCodes.OK,
entity = w.toString,
headers = `Last-Modified`(DateTime(1970, 1, 1)) :: metadataToHeaderList(meta)
entity = w.toString
)
}

Expand Down
Loading