From 4c64e95f2444bd84622bf87cee65fc3241c20825 Mon Sep 17 00:00:00 2001 From: "Bala.FA" Date: Wed, 30 Sep 2020 19:10:35 +0530 Subject: [PATCH] add {set,get,delete}_{bucket,object}_tags apis --- docs/API.md | 139 +++++++++++++++++++++++++++++- examples/delete_bucket_tags.py | 28 +++++++ examples/delete_object_tags.py | 28 +++++++ examples/get_bucket_tags.py | 28 +++++++ examples/get_object_tags.py | 28 +++++++ examples/set_bucket_tags.py | 32 +++++++ examples/set_object_tags.py | 32 +++++++ minio/api.py | 144 ++++++++++++++++++++++++++++++++ minio/commonconfig.py | 10 +++ minio/tagging.py | 52 ++++++++++++ tests/unit/generate_xml_test.py | 3 +- tests/unit/tagging.py | 47 +++++++++++ 12 files changed, 566 insertions(+), 5 deletions(-) create mode 100644 examples/delete_bucket_tags.py create mode 100644 examples/delete_object_tags.py create mode 100644 examples/get_bucket_tags.py create mode 100644 examples/get_object_tags.py create mode 100644 examples/set_bucket_tags.py create mode 100644 examples/set_object_tags.py create mode 100644 minio/tagging.py create mode 100644 tests/unit/tagging.py diff --git a/docs/API.md b/docs/API.md index 584f2a3d5..ea709f81e 100644 --- a/docs/API.md +++ b/docs/API.md @@ -42,9 +42,12 @@ s3Client = Minio( | [`delete_bucket_replication`](#delete_bucket_replication) | [`fget_object`](#fget_object) | | [`get_bucket_encryption`](#get_bucket_encryption) | | [`get_bucket_replication`](#get_bucket_replication) | [`remove_objects`](#remove_objects) | | [`remove_all_bucket_notification`](#remove_all_bucket_notification) | | [`set_bucket_replication`](#set_bucket_replication) | [`select_object_content`](#select_object_content) | | [`put_bucket_encryption`](#put_bucket_encryption) | -| [`delete_bucket_lifecycle`](#delete_bucket_lifecycle) | | | [`delete_bucket_encryption`](#delete_bucket_encryption) | -| [`get_bucket_lifecycle`](#get_bucket_lifecycle) | | | | -| [`set_bucket_lifecycle`](#set_bucket_lifecycle) | | | | +| [`delete_bucket_lifecycle`](#delete_bucket_lifecycle) | [`delete_object_tags`](#delete_object_tags) | | [`delete_bucket_encryption`](#delete_bucket_encryption) | +| [`get_bucket_lifecycle`](#get_bucket_lifecycle) | [`get_object_tags`](#get_object_tags) | | | +| [`set_bucket_lifecycle`](#set_bucket_lifecycle) | [`set_object_tags`](#set_object_tags) | | | +| [`delete_bucket_tags`](#delete_bucket_tags) | | | | +| [`get_bucket_tags`](#get_bucket_tags) | | | | +| [`set_bucket_tags`](#set_bucket_tags) | | | | ## 1. Constructor @@ -776,6 +779,68 @@ config = LifecycleConfig( minio.set_bucket_lifecycle("my-bucketname", config) ``` + + +### delete_bucket_tags(bucket_name) + +Delete tags configuration of a bucket. + +__Parameters__ + +| Param | Type | Description | +|:----------------|:------|:--------------------| +| ``bucket_name`` | _str_ | Name of the bucket. | + +__Example__ + +```py +minio.delete_bucket_tags("my-bucketname") +``` + + + +### get_bucket_tags(bucket_name) + +Get tags configuration of a bucket. + +__Parameters__ + +| Param | Type | Description | +|:----------------|:------|:--------------------| +| ``bucket_name`` | _str_ | Name of the bucket. | + +| Return | +|:---------------| +| _Tags_ object. | + +__Example__ + +```py +tags = minio.get_bucket_tags("my-bucketname") +``` + + + +### set_bucket_tags(bucket_name, tags) + +Set tags configuration to a bucket. + +__Parameters__ + +| Param | Type | Description | +|:----------------|:-------|:--------------------| +| ``bucket_name`` | _str_ | Name of the bucket. | +| ``tags`` | _Tags_ | Tags configuration. | + +__Example__ + +```py +tags = Tags.new_bucket_tags() +tags["Project"] = "Project One" +tags["User"] = "jsmith" +client.set_bucket_tags("my-bucketname", tags) +``` + ## 3. Object operations @@ -1134,6 +1199,74 @@ minio.remove_objects( ) ``` + + +### delete_object_tags(bucket_name, object_name, version_id=None) + +Delete tags configuration of an object. + +__Parameters__ + +| Param | Type | Description | +|:----------------|:------|:---------------------------| +| ``bucket_name`` | _str_ | Name of the bucket. | +| ``object_name`` | _str_ | Object name in the bucket. | +| ``version_id`` | _str_ | Version ID of the object. | + +__Example__ + +```py +minio.delete_object_tags("my-bucketname", "my-objectname") +``` + + + +### get_object_tags(bucket_name, object_name, version_id=None) + +Get tags configuration of an object. + +__Parameters__ + +| Param | Type | Description | +|:----------------|:------|:---------------------------| +| ``bucket_name`` | _str_ | Name of the bucket. | +| ``object_name`` | _str_ | Object name in the bucket. | +| ``version_id`` | _str_ | Version ID of the object. | + +| Return | +|:---------------| +| _Tags_ object. | + +__Example__ + +```py +tags = minio.get_object_tags("my-bucketname", "my-objectname") +``` + + + +### set_object_tags(bucket_name, object_name, tags, version_id=None) + +Set tags configuration to an object. + +__Parameters__ + +| Param | Type | Description | +|:----------------|:-------|:---------------------------| +| ``bucket_name`` | _str_ | Name of the bucket. | +| ``object_name`` | _str_ | Object name in the bucket. | +| ``tags`` | _Tags_ | Tags configuration. | +| ``version_id`` | _str_ | Version ID of the object. | + +__Example__ + +```py +tags = Tags.new_object_tags() +tags["Project"] = "Project One" +tags["User"] = "jsmith" +client.set_object_tags("my-bucketname", "my-objectname", tags) +``` + ## 4. Presigned operations diff --git a/examples/delete_bucket_tags.py b/examples/delete_bucket_tags.py new file mode 100644 index 000000000..f77672628 --- /dev/null +++ b/examples/delete_bucket_tags.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# MinIO Python Library for Amazon S3 Compatible Cloud Storage. +# Copyright (C) 2020 MinIO, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are +# dummy values, please replace them with original values. + +from minio import Minio + +client = Minio( + "play.min.io", + access_key="Q3AM3UQ867SPQQA43P2F", + secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", +) + +client.delete_bucket_tags("my-bucketname") diff --git a/examples/delete_object_tags.py b/examples/delete_object_tags.py new file mode 100644 index 000000000..79cf31e59 --- /dev/null +++ b/examples/delete_object_tags.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# MinIO Python Library for Amazon S3 Compatible Cloud Storage. +# Copyright (C) 2020 MinIO, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are +# dummy values, please replace them with original values. + +from minio import Minio + +client = Minio( + "play.min.io", + access_key="Q3AM3UQ867SPQQA43P2F", + secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", +) + +client.delete_object_tags("my-bucketname", "my-objectname") diff --git a/examples/get_bucket_tags.py b/examples/get_bucket_tags.py new file mode 100644 index 000000000..7c2b11b43 --- /dev/null +++ b/examples/get_bucket_tags.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# MinIO Python Library for Amazon S3 Compatible Cloud Storage. +# Copyright (C) 2020 MinIO, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are +# dummy values, please replace them with original values. + +from minio import Minio + +client = Minio( + "play.min.io", + access_key="Q3AM3UQ867SPQQA43P2F", + secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", +) + +tags = client.get_bucket_tags("my-bucketname") diff --git a/examples/get_object_tags.py b/examples/get_object_tags.py new file mode 100644 index 000000000..f1e40c733 --- /dev/null +++ b/examples/get_object_tags.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# MinIO Python Library for Amazon S3 Compatible Cloud Storage. +# Copyright (C) 2020 MinIO, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are +# dummy values, please replace them with original values. + +from minio import Minio + +client = Minio( + "play.min.io", + access_key="Q3AM3UQ867SPQQA43P2F", + secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", +) + +tags = client.get_object_tags("my-bucketname", "my-objectname") diff --git a/examples/set_bucket_tags.py b/examples/set_bucket_tags.py new file mode 100644 index 000000000..9277fb8fb --- /dev/null +++ b/examples/set_bucket_tags.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# MinIO Python Library for Amazon S3 Compatible Cloud Storage. +# Copyright (C) 2020 MinIO, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are +# dummy values, please replace them with original values. + +from minio import Minio +from minio.commonconfig import Tags + +client = Minio( + "play.min.io", + access_key="Q3AM3UQ867SPQQA43P2F", + secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", +) + +tags = Tags.new_bucket_tags() +tags["Project"] = "Project One" +tags["User"] = "jsmith" +client.set_bucket_tags("my-bucketname", tags) diff --git a/examples/set_object_tags.py b/examples/set_object_tags.py new file mode 100644 index 000000000..c7683e0cb --- /dev/null +++ b/examples/set_object_tags.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# MinIO Python Library for Amazon S3 Compatible Cloud Storage. +# Copyright (C) 2020 MinIO, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY and my-bucketname are +# dummy values, please replace them with original values. + +from minio import Minio +from minio.commonconfig import Tags + +client = Minio( + "play.min.io", + access_key="Q3AM3UQ867SPQQA43P2F", + secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", +) + +tags = Tags.new_object_tags() +tags["Project"] = "Project One" +tags["User"] = "jsmith" +client.set_object_tags("my-bucketname", "my-objectname", tags) diff --git a/minio/api.py b/minio/api.py index 6cd091d77..b18564304 100644 --- a/minio/api.py +++ b/minio/api.py @@ -43,6 +43,7 @@ import urllib3 from . import __title__, __version__ +from .commonconfig import Tags from .credentials import StaticProvider from .definitions import BaseURL, Object, Part from .error import InvalidResponseError, S3Error, ServerError @@ -65,6 +66,7 @@ from .signer import (AMZ_DATE_FORMAT, SIGN_V4_ALGORITHM, get_credential_string, post_presign_v4, presign_v4, sign_v4_s3) from .sse import SseCustomerKey +from .tagging import Tagging from .thread_pool import ThreadPool from .xml import Element, SubElement, marshal, unmarshal from .xml_marshal import (marshal_bucket_notifications, @@ -1902,6 +1904,148 @@ def set_bucket_lifecycle(self, bucket_name, config): query_params={"lifecycle": ""}, ) + def delete_bucket_tags(self, bucket_name): + """ + Delete tags configuration of a bucket. + + :param bucket_name: Name of the bucket. + + Example:: + minio.delete_bucket_tags("my-bucketname") + """ + check_bucket_name(bucket_name) + self._execute("DELETE", bucket_name, query_params={"tagging": ""}) + + def get_bucket_tags(self, bucket_name): + """ + Get tags configuration of a bucket. + + :param bucket_name: Name of the bucket. + :return: :class:`Tags ` object. + + Example:: + tags = minio.get_bucket_tags("my-bucketname") + """ + check_bucket_name(bucket_name) + try: + response = self._execute( + "GET", bucket_name, query_params={"tagging": ""}, + ) + tagging = unmarshal(Tagging, response.data.decode()) + return tagging.tags() + except S3Error as exc: + if exc.code != "NoSuchTagSet": + raise + return None + + def set_bucket_tags(self, bucket_name, tags): + """ + Set tags configuration to a bucket. + + :param bucket_name: Name of the bucket. + :param config: :class:`Tags ` object. + + Example:: + tags = Tags.new_bucket_tags() + tags["Project"] = "Project One" + tags["User"] = "jsmith" + minio.set_bucket_tags("my-bucketname", tags) + """ + check_bucket_name(bucket_name) + if not isinstance(tags, Tags): + raise ValueError("tags must be Tags type") + body = marshal(Tagging(tags)) + self._execute( + "PUT", + bucket_name, + body=body, + headers={"Content-MD5": md5sum_hash(body)}, + query_params={"tagging": ""}, + ) + + def delete_object_tags(self, bucket_name, object_name, version_id=None): + """ + Delete tags configuration of an object. + + :param bucket_name: Name of the bucket. + :param object_name: Object name in the bucket. + :param version_id: Version ID of the Object. + + Example:: + minio.delete_object_tags("my-bucketname", "my-objectname") + """ + check_bucket_name(bucket_name) + check_non_empty_string(object_name) + query_params = {"versionId": version_id} if version_id else {} + query_params["tagging"] = "" + self._execute( + "DELETE", + bucket_name, + object_name=object_name, + query_params=query_params, + ) + + def get_object_tags(self, bucket_name, object_name, version_id=None): + """ + Get tags configuration of a object. + + :param bucket_name: Name of the bucket. + :param object_name: Object name in the bucket. + :param version_id: Version ID of the Object. + :return: :class:`Tags ` object. + + Example:: + tags = minio.get_object_tags("my-bucketname", "my-objectname") + """ + check_bucket_name(bucket_name) + check_non_empty_string(object_name) + query_params = {"versionId": version_id} if version_id else {} + query_params["tagging"] = "" + try: + response = self._execute( + "GET", + bucket_name, + object_name=object_name, + query_params=query_params, + ) + tagging = unmarshal(Tagging, response.data.decode()) + return tagging.tags() + except S3Error as exc: + if exc.code != "NoSuchTagSet": + raise + return None + + def set_object_tags(self, bucket_name, object_name, tags, version_id=None): + """ + Set tags configuration to an object. + + :param bucket_name: Name of the bucket. + :param object_name: Object name in the bucket. + :param version_id: Version ID of the Object. + :param config: :class:`Tags ` object. + + Example:: + tags = Tags.new_object_tags() + tags["Project"] = "Project One" + tags["User"] = "jsmith" + minio.set_object_tags("my-bucketname", "my-objectname", tags) + """ + check_bucket_name(bucket_name) + check_non_empty_string(object_name) + if not isinstance(tags, Tags): + raise ValueError("tags must be Tags type") + body = marshal(Tagging(tags)) + query_params = {"versionId": version_id} if version_id else {} + query_params["tagging"] = "" + self._execute( + "PUT", + bucket_name, + object_name=object_name, + body=body, + headers={"Content-MD5": md5sum_hash(body)}, + query_params=query_params, + ) + def _list_objects( # pylint: disable=too-many-arguments,too-many-branches self, bucket_name, diff --git a/minio/commonconfig.py b/minio/commonconfig.py index c59179851..85bf6d7d7 100644 --- a/minio/commonconfig.py +++ b/minio/commonconfig.py @@ -52,6 +52,16 @@ def __setitem__(self, key, value): raise ValueError("invalid tag value '{0}'".format(value)) super().__setitem__(key, value) + @classmethod + def new_bucket_tags(cls): + """Create new bucket tags.""" + return cls() + + @classmethod + def new_object_tags(cls): + """Create new object tags.""" + return cls(True) + @classmethod def fromxml(cls, element): """Create new object with values from XML element.""" diff --git a/minio/tagging.py b/minio/tagging.py new file mode 100644 index 000000000..ac186ab45 --- /dev/null +++ b/minio/tagging.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# MinIO Python Library for Amazon S3 Compatible Cloud Storage, (C) +# 2020 MinIO, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tagging for bucket and object.""" + +from __future__ import absolute_import + +from .commonconfig import Tags +from .xml import Element, SubElement, find + + +class Tagging: + """Tagging for buckets and objects.""" + + def __init__(self, tags): + self._tags = tags + + @property + def tags(self): + """Get tags.""" + return self._tags + + @classmethod + def fromxml(cls, element): + """Create new object with values from XML element.""" + element = find(element, "TagSet") + tags = ( + None if find(element, "Tag") is None + else Tags.fromxml(element) + ) + return cls(tags) + + def toxml(self, element): + """Convert to XML.""" + element = Element("Tagging") + if self._tags: + element = SubElement(element, "TagSet") + self._tags.toxml(element) + return element diff --git a/tests/unit/generate_xml_test.py b/tests/unit/generate_xml_test.py index cd26b3250..0210c7bd9 100644 --- a/tests/unit/generate_xml_test.py +++ b/tests/unit/generate_xml_test.py @@ -22,8 +22,7 @@ from minio.select.options import (CSVInput, CSVOutput, InputSerialization, OutputSerialization, RequestProgress, SelectObjectOptions) -from minio.xml_marshal import (marshal_complete_multipart, - xml_marshal_select) +from minio.xml_marshal import marshal_complete_multipart, xml_marshal_select class GenerateRequestTest(TestCase): diff --git a/tests/unit/tagging.py b/tests/unit/tagging.py new file mode 100644 index 000000000..b3ff77295 --- /dev/null +++ b/tests/unit/tagging.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# MinIO Python Library for Amazon S3 Compatible Cloud Storage, +# (C) 2020 MinIO, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest import TestCase + +from minio import xml +from minio.commonconfig import Tags +from minio.tagging import Tagging + + +class TaggingTest(TestCase): + def test_tagging(self): + tags = Tags() + tags["Project"] = "Project One" + tags["User"] = "jsmith" + tagging = Tagging(tags) + xml.marshal(tagging) + + config = xml.unmarshal( + Tagging, + """ + + + key1 + value1 + + + key2 + value2 + + +""", + ) + xml.marshal(config)