Repository: getmoto/moto

Issue:
s3.copy_object doesn't honor tagging directive
When I copy an S3 object in moto the tagset is always copied with it, even when I request a replacement tagset.

For example:

```python
import moto
import boto3

BUCKET = 'testbucket'

with moto.mock_s3():
    c = boto3.client('s3')
    c.create_bucket(Bucket=BUCKET)
    c.put_object(Bucket=BUCKET, Key='src', Body=b'', Tagging='tags=old')
    c.copy_object(
        CopySource={'Bucket': BUCKET, 'Key': 'src'},
        Bucket=BUCKET,
        Key='dst',
        TaggingDirective='REPLACE',
        Tagging='tags=new',
    )
    r = c.get_object_tagging(Bucket=BUCKET, Key='dst')
    print('tags for new object (moto s3):', r['TagSet'])
```

Prints:

```
tags for new object (moto s3): [{'Key': 'tags', 'Value': 'old'}]
```

But when running this in actual S3:

```python
import boto3

BUCKET = '...'

c = boto3.client('s3')
c.put_object(Bucket=BUCKET, Key='src', Body=b'', Tagging='tags=old')
c.copy_object(
    CopySource={'Bucket': BUCKET, 'Key': 'src'},
    Bucket=BUCKET,
    Key='dst',
    TaggingDirective='REPLACE',
    Tagging='tags=new',
)
r = c.get_object_tagging(Bucket=BUCKET, Key='dst')
print('tags for new object (real s3):', r['TagSet'])
```

I get the expected output:

```
tags for new object (real s3): [{'Key': 'tags', 'Value': 'new'}]
```

Versions used:

moto==1.3.7
boto3==1.7.47
botocore==1.12.123



Discussion:

Resolving patch:
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -1051,6 +1051,10 @@ def _key_response_put(self, request, body, bucket_name, query, key_name, headers
             if mdirective is not None and mdirective == "REPLACE":
                 metadata = metadata_from_headers(request.headers)
                 new_key.set_metadata(metadata, replace=True)
+            tdirective = request.headers.get("x-amz-tagging-directive")
+            if tdirective == "REPLACE":
+                tagging = self._tagging_from_headers(request.headers)
+                new_key.set_tagging(tagging)
             template = self.response_template(S3_OBJECT_COPY_RESPONSE)
             response_headers.update(new_key.response_dict)
             return 200, response_headers, template.render(key=new_key)


Test patch:
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -1727,6 +1727,34 @@ def test_boto3_copy_object_from_unversioned_to_versioned_bucket():
     obj2_version_new.should_not.equal(None)
 
 
+@mock_s3
+def test_boto3_copy_object_with_replacement_tagging():
+    client = boto3.client("s3", region_name="eu-north-1")
+    client.create_bucket(Bucket="mybucket")
+    client.put_object(
+        Bucket="mybucket", Key="original", Body=b"test", Tagging="tag=old"
+    )
+
+    client.copy_object(
+        CopySource={"Bucket": "mybucket", "Key": "original"},
+        Bucket="mybucket",
+        Key="copy1",
+        TaggingDirective="REPLACE",
+        Tagging="tag=new",
+    )
+    client.copy_object(
+        CopySource={"Bucket": "mybucket", "Key": "original"},
+        Bucket="mybucket",
+        Key="copy2",
+        TaggingDirective="COPY",
+    )
+
+    tags1 = client.get_object_tagging(Bucket="mybucket", Key="copy1")["TagSet"]
+    tags1.should.equal([{"Key": "tag", "Value": "new"}])
+    tags2 = client.get_object_tagging(Bucket="mybucket", Key="copy2")["TagSet"]
+    tags2.should.equal([{"Key": "tag", "Value": "old"}])
+
+
 @mock_s3
 def test_boto3_deleted_versionings_list():
     client = boto3.client("s3", region_name="us-east-1")