diff --git a/S3/Crypto.py b/S3/Crypto.py
index 2dda8be7..72302ed7 100644
--- a/S3/Crypto.py
+++ b/S3/Crypto.py
@@ -89,9 +89,9 @@ def sign_request_v2(method='GET', canonical_uri='/', params=None, cur_headers=No
# valid sub-resources to be included in sign v2:
SUBRESOURCES_TO_INCLUDE = ['acl', 'lifecycle', 'location', 'logging',
'notification', 'partNumber', 'policy',
- 'requestPayment', 'torrent', 'uploadId',
- 'uploads', 'versionId', 'versioning',
- 'versions', 'website',
+ 'requestPayment', 'tagging', 'torrent',
+ 'uploadId', 'uploads', 'versionId',
+ 'versioning', 'versions', 'website',
# Missing of aws s3 doc but needed
'delete', 'cors', 'restore']
diff --git a/S3/S3.py b/S3/S3.py
index aec5358f..87cee70d 100644
--- a/S3/S3.py
+++ b/S3/S3.py
@@ -1317,6 +1317,58 @@ def delete_notification_policy(self, uri):
empty_config = ''
return self.set_notification_policy(uri, empty_config)
+ def set_tagging(self, uri, tagsets):
+ if uri.type != "s3":
+ raise ValueError("Expected URI type 's3', got '%s'" % uri.type)
+ body = ''
+ body += ''
+ for (key, val) in tagsets:
+ body += ''
+ body += (' %s' % key)
+ body += (' %s' % val)
+ body += ''
+ body += ''
+ body += ''
+ headers = SortedDict(ignore_case=True)
+ headers['content-md5'] = generate_content_md5(body)
+ if uri.has_object():
+ request = self.create_request("OBJECT_PUT", uri=uri,
+ headers=headers, body=body,
+ uri_params={'tagging': None})
+ else:
+ request = self.create_request("BUCKET_CREATE", bucket=uri.bucket(),
+ headers=headers, body=body,
+ uri_params={'tagging': None})
+ debug(u"set_tagging(%s): tagset-xml: %s" % (uri, body))
+ response = self.send_request(request)
+ return response
+
+ def get_tagging(self, uri):
+ if uri.has_object():
+ request = self.create_request("OBJECT_GET", uri=uri,
+ uri_params={'tagging': None})
+ else:
+ request = self.create_request("BUCKET_LIST", bucket=uri.bucket(),
+ uri_params={'tagging': None})
+ debug(u"get_tagging(%s)" % uri)
+ response = self.send_request(request)
+ xml_data = response["data"]
+ # extract list of tag sets
+ tagsets = getListFromXml(xml_data, "Tag")
+ debug(u"%s: Got object tagging" % response['status'])
+ return tagsets
+
+ def delete_tagging(self, uri):
+ if uri.has_object():
+ request = self.create_request("OBJECT_DELETE", uri=uri,
+ uri_params={'tagging': None})
+ else:
+ request = self.create_request("BUCKET_DELETE", bucket=uri.bucket(),
+ uri_params={'tagging': None})
+ debug(u"delete_tagging(%s)" % uri)
+ response = self.send_request(request)
+ return response
+
def get_multipart(self, uri, uri_params=None, limit=-1):
upload_list = []
for truncated, uploads in self.get_multipart_streaming(uri,
diff --git a/s3cmd b/s3cmd
index a5f6631c..85174341 100755
--- a/s3cmd
+++ b/s3cmd
@@ -2476,6 +2476,52 @@ def cmd_delnotification(args):
output(u"%s: Notification Policy deleted" % uri)
return EX_OK
+def cmd_settagging(args):
+ s3 = S3(Config())
+ uri = S3Uri(args[0])
+ tag_set_string = args[1]
+
+ tagsets = [
+ tuple(tagset.split("="))
+ for tagset in tag_set_string.split("&")
+ ]
+ debug(tagsets)
+ response = s3.set_tagging(uri, tagsets)
+
+ debug(u"response - %s" % response['status'])
+ if response['status'] in [200, 204]:
+ output(u"%s: Tagging updated" % uri)
+ return EX_OK
+
+def cmd_gettagging(args):
+ s3 = S3(Config())
+ uri = S3Uri(args[0])
+
+ tagsets = s3.get_tagging(uri)
+ if uri.has_object():
+ output(u"%s (object):" % uri)
+ else:
+ output(u"%s (bucket):" % uri)
+ debug(tagsets)
+ for tag in tagsets:
+ try:
+ output(u"\t%s:\t%s" % (
+ tag['Key'],
+ tag['Value']))
+ except KeyError:
+ pass
+ return EX_OK
+
+def cmd_deltagging(args):
+ s3 = S3(Config())
+ uri = S3Uri(args[0])
+
+ response = s3.delete_tagging(uri)
+
+ debug(u"response - %s" % response['status'])
+ output(u"%s: Tagging deleted" % uri)
+ return EX_OK
+
def cmd_multipart(args):
cfg = Config()
s3 = S3(cfg)
@@ -2947,6 +2993,11 @@ def get_commands_list():
{"cmd":"signurl", "label":"Sign an S3 URL to provide limited public access with expiry", "param":"s3://BUCKET/OBJECT ", "func":cmd_signurl, "argc":2},
{"cmd":"fixbucket", "label":"Fix invalid file names in a bucket", "param":"s3://BUCKET[/PREFIX]", "func":cmd_fixbucket, "argc":1},
+ ## Tagging commands
+ {"cmd":"settagging", "label":"Modify tagging for Bucket or Files", "param":"s3://BUCKET[/OBJECT] \"KEY=VALUE[&KEY=VALUE ...]\"", "func":cmd_settagging, "argc":2},
+ {"cmd":"gettagging", "label":"Get tagging for Bucket or Files", "param":"s3://BUCKET[/OBJECT]", "func":cmd_gettagging, "argc":1},
+ {"cmd":"deltagging", "label":"Delete tagging for Bucket or Files", "param":"s3://BUCKET[/OBJECT]", "func":cmd_deltagging, "argc":1},
+
## Website commands
{"cmd":"ws-create", "label":"Create Website from bucket", "param":"s3://BUCKET", "func":cmd_website_create, "argc":1},
{"cmd":"ws-delete", "label":"Delete Website", "param":"s3://BUCKET", "func":cmd_website_delete, "argc":1},