diff --git a/docs/API.md b/docs/API.md index 38016f55a..d2cfb6a74 100644 --- a/docs/API.md +++ b/docs/API.md @@ -37,8 +37,8 @@ s3Client = Minio( | [`bucket_exists`](#bucket_exists) | [`copy_object`](#copy_object) | [`presigned_post_policy`](#presigned_post_policy) | [`delete_bucket_policy`](#delete_bucket_policy) | | [`remove_bucket`](#remove_bucket) | [`stat_object`](#stat_object) | | [`get_bucket_notification`](#get_bucket_notification) | | [`list_objects`](#list_objects) | [`remove_object`](#remove_object) | | [`set_bucket_notification`](#set_bucket_notification) | -| [`enable_bucket_versioning`](#enable_bucket_versioning) | [`remove_objects`](#remove_objects) | | [`remove_all_bucket_notification`](#remove_all_bucket_notification) | -| [`disable_bucket_versioning`](#disable_bucket_versioning) | [`fput_object`](#fput_object) | | [`listen_bucket_notification`](#listen_bucket_notification) | +| [`get_bucket_versioning`](#get_bucket_versioning) | [`remove_objects`](#remove_objects) | | [`remove_all_bucket_notification`](#remove_all_bucket_notification) | +| [`set_bucket_versioning`](#set_bucket_versioning) | [`fput_object`](#fput_object) | | [`listen_bucket_notification`](#listen_bucket_notification) | | | [`fget_object`](#fget_object) | | [`get_bucket_encryption`](#get_bucket_encryption) | | | [`select_object_content`](#select_object_content) | | [`put_bucket_encryption`](#put_bucket_encryption) | | | | | [`delete_bucket_encryption`](#delete_bucket_encryption) | @@ -570,11 +570,11 @@ __Example__ minio.delete_bucket_encryption("my-bucketname") ``` - + -### enable_bucket_versioning(bucket_name) +### get_bucket_versioning(bucket_name) -Enable object versioning feature in a bucket. +Get versioning configuration of a bucket. __Parameters__ @@ -585,25 +585,27 @@ __Parameters__ __Example__ ```py -minio.enable_bucket_versioning("my-bucketname") +config = minio.get_bucket_versioning("my-bucketname") +print(config.status) ``` - + -### disable_bucket_versioning(bucket_name) +### set_bucket_versioning(bucket_name, config) -Disable object versioning feature in a bucket. +Set versioning configuration to a bucket. __Parameters__ -| Param | Type | Description | -|:----------------|:------|:--------------------| -| ``bucket_name`` | _str_ | Name of the bucket. | +| Param | Type | Description | +|:----------------|:-------------------|:--------------------------| +| ``bucket_name`` | _str_ | Name of the bucket. | +| ``config`` | _VersioningConfig_ | Versioning configuration. | __Example__ ```py -minio.disable_bucket_versioning("my-bucketname") +minio.set_bucket_versioning("my-bucketname", VersioningConfig("Enabled")) ``` ## 3. Object operations diff --git a/minio/__init__.py b/minio/__init__.py index bf0925cfd..290667516 100644 --- a/minio/__init__.py +++ b/minio/__init__.py @@ -36,6 +36,6 @@ # pylint: disable=unused-import from .api import Minio from .copy_conditions import CopyConditions -from .definitions import Bucket, Object +from .definitions import Bucket, Object, VersioningConfig from .error import InvalidResponseError, S3Error, ServerError from .post_policy import PostPolicy diff --git a/minio/api.py b/minio/api.py index 517fd4ebb..bb055bf92 100644 --- a/minio/api.py +++ b/minio/api.py @@ -58,7 +58,7 @@ parse_list_parts, parse_location_constraint, parse_multi_delete_response, parse_multipart_upload_result, - parse_new_multipart_upload) + parse_new_multipart_upload, parse_versioning_config) from .select import SelectObjectReader from .signer import (AMZ_DATE_FORMAT, SIGN_V4_ALGORITHM, get_credential_string, post_presign_v4, presign_v4, sign_v4_s3) @@ -66,6 +66,7 @@ from .thread_pool import ThreadPool from .xml_marshal import (marshal_bucket_notifications, marshal_complete_multipart, + marshal_versioning_config, xml_marshal_bucket_constraint, xml_marshal_bucket_encryption, xml_marshal_delete_objects, xml_marshal_select, @@ -864,14 +865,20 @@ def listen_bucket_notification(self, bucket_name, prefix='', suffix='', response.close() response.release_conn() - def _do_bucket_versioning(self, bucket_name, status): - """Do versioning support in a bucket.""" + def set_bucket_versioning(self, bucket_name, config): + """ + Set versioning configuration to a bucket. + + :param bucket_name: Name of the bucket. + :param config: :class:`VersioningConfig `. + + Example:: + minio.set_bucket_versioning( + "my-bucketname", VersioningConfig("Enabled"), + ) + """ check_bucket_name(bucket_name) - body = ( - '' - '{0}' - ).format(status).encode() + body = marshal_versioning_config(config) self._execute( "PUT", bucket_name, @@ -880,27 +887,25 @@ def _do_bucket_versioning(self, bucket_name, status): query_params={"versioning": ""}, ) - def enable_bucket_versioning(self, bucket_name): + def get_bucket_versioning(self, bucket_name): """ - Enable object versioning feature in a bucket. + Get versioning configuration of a bucket. :param bucket_name: Name of the bucket. + :return: :class:`VersioningConfig `. Example:: - minio.enable_bucket_versioning("my-bucketname") - """ - self._do_bucket_versioning(bucket_name, "Enabled") - - def disable_bucket_versioning(self, bucket_name): + config minio.get_bucket_versioning("my-bucketname") + print(config.status) """ - Disable object versioning feature in a bucket. - - :param bucket_name: Name of the bucket. + check_bucket_name(bucket_name) + response = self._execute( + "GET", + bucket_name, + query_params={"versioning": ""}, + ) - Example:: - minio.disable_bucket_versioning("my-bucketname") - """ - self._do_bucket_versioning(bucket_name, "Suspended") + return parse_versioning_config(response.data) def fput_object(self, bucket_name, object_name, file_path, content_type='application/octet-stream', diff --git a/minio/definitions.py b/minio/definitions.py index af4e142d4..be135c934 100644 --- a/minio/definitions.py +++ b/minio/definitions.py @@ -472,3 +472,25 @@ def __init__(self, root): self.parts = [ Part(part_element) for part_element in root.findall("Part") ] + + +class VersioningConfig: + """Bucket versioning configuration.""" + + def __init__(self, status, mfa_delete=None): + if status: + status = status.title() + if status not in ["", "Enabled", "Suspended"]: + raise ValueError("status must be empty, Enabled or Suspended.") + self._status = status + self._mfa_delete = mfa_delete + + @property + def status(self): + """Get status.""" + return self._status or "Off" + + @property + def mfa_delete(self): + """Get MFA delete.""" + return self._mfa_delete diff --git a/minio/parsers.py b/minio/parsers.py index 1a1da9aed..94d3bee7b 100644 --- a/minio/parsers.py +++ b/minio/parsers.py @@ -31,7 +31,8 @@ from xml.etree.ElementTree import ParseError from .definitions import (Bucket, CopyObjectResult, ListMultipartUploadsResult, - ListPartsResult, MultipartUploadResult, Object) + ListPartsResult, MultipartUploadResult, Object, + VersioningConfig) # minio specific. from .error import MultiDeleteError, S3Error from .helpers import RFC3339, RFC3339NANO @@ -487,3 +488,12 @@ def parse_list_multipart_uploads(data): def parse_list_parts(data): """Parse ListParts API resppnse XML.""" return ListPartsResult(S3Element.fromstring("ListPartsResult", data)) + + +def parse_versioning_config(data): + """Decode XML data into :class:`VersioningConfig `""" + root = S3Element.fromstring("VersioningConfiguration", data) + return VersioningConfig( + root.get_child_text("Status"), + root.get_child_text("MFADelete"), + ) diff --git a/minio/xml_marshal.py b/minio/xml_marshal.py index da922cec4..969212b87 100644 --- a/minio/xml_marshal.py +++ b/minio/xml_marshal.py @@ -409,3 +409,14 @@ def xml_marshal_delete_objects(keys): SubElement(element, "VersionId", version_id) return _get_xml_data(root) + + +def marshal_versioning_config(config): + """Encode versioning configuration to XML bytes.""" + root = Element("VersioningConfiguration", with_namespace=True) + SubElement(root, "Status", config.status) + if config.mfa_delete is not None: + SubElement( + root, "MFADelete", "Enabled" if config.mfa_delete else "Disabled", + ) + return _get_xml_data(root) diff --git a/tests/functional/tests.py b/tests/functional/tests.py index 25fd83df3..91cf0ae69 100644 --- a/tests/functional/tests.py +++ b/tests/functional/tests.py @@ -38,7 +38,7 @@ import certifi import urllib3 -from minio import CopyConditions, Minio, PostPolicy +from minio import CopyConditions, Minio, PostPolicy, VersioningConfig from minio.error import S3Error from minio.select.helpers import calculate_crc from minio.select.options import (CSVInput, CSVOutput, InputSerialization, @@ -807,7 +807,9 @@ def _test_stat_object(log_entry, sse=None, version_check=False): _CLIENT.make_bucket(bucket_name) try: if version_check: - _CLIENT.enable_bucket_versioning(bucket_name) + _CLIENT.set_bucket_versioning( + bucket_name, VersioningConfig("Enabled"), + ) # Put/Upload a streaming object of 1 MiB reader = LimitedRandomReader(length) _, version_id1 = _CLIENT.put_object( @@ -875,7 +877,9 @@ def _test_remove_object(log_entry, version_check=False): _CLIENT.make_bucket(bucket_name) try: if version_check: - _CLIENT.enable_bucket_versioning(bucket_name) + _CLIENT.set_bucket_versioning( + bucket_name, VersioningConfig("Enabled"), + ) _, version_id = _CLIENT.put_object( bucket_name, object_name, LimitedRandomReader(length), length, ) @@ -914,7 +918,9 @@ def _test_get_object(log_entry, sse=None, version_check=False): version_id = None try: if version_check: - _CLIENT.enable_bucket_versioning(bucket_name) + _CLIENT.set_bucket_versioning( + bucket_name, VersioningConfig("Enabled"), + ) _, version_id = _CLIENT.put_object( bucket_name, object_name, LimitedRandomReader(length), length, sse=sse, @@ -965,7 +971,9 @@ def _test_fget_object(log_entry, sse=None, version_check=False): version_id = None try: if version_check: - _CLIENT.enable_bucket_versioning(bucket_name) + _CLIENT.set_bucket_versioning( + bucket_name, VersioningConfig("Enabled"), + ) _, version_id = _CLIENT.put_object( bucket_name, object_name, LimitedRandomReader(length), length, sse=sse, @@ -1090,7 +1098,9 @@ def _test_list_objects(log_entry, use_api_v1=False, version_check=False): version_id2 = None try: if version_check: - _CLIENT.enable_bucket_versioning(bucket_name) + _CLIENT.set_bucket_versioning( + bucket_name, VersioningConfig("Enabled"), + ) size = 1 * KB _, version_id1 = _CLIENT.put_object( bucket_name, object_name + "-1", LimitedRandomReader(size), size, @@ -1469,7 +1479,7 @@ def test_presigned_get_object_version( # pylint: disable=invalid-name _CLIENT.make_bucket(bucket_name) version_id = None try: - _CLIENT.enable_bucket_versioning(bucket_name) + _CLIENT.set_bucket_versioning(bucket_name, VersioningConfig("Enabled")) size = 1 * KB _, version_id = _CLIENT.put_object( bucket_name, object_name, LimitedRandomReader(size), size, @@ -1835,7 +1845,9 @@ def _test_remove_objects(log_entry, version_check=False): object_names = [] try: if version_check: - _CLIENT.enable_bucket_versioning(bucket_name) + _CLIENT.set_bucket_versioning( + bucket_name, VersioningConfig("Enabled"), + ) size = 1 * KB # Upload some new objects to prepare for multi-object delete test. for i in range(10):