diff --git a/Makefile b/Makefile index 75f512240..fb004cb79 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ check: @pip install --user --upgrade pylint - @if python --version | grep -qi 'python 3'; then pylint --reports=no --score=no --disable=R0401 minio/*py; fi + @if python --version | grep -qi 'python 3'; then pylint --reports=no --score=no --disable=R0401,R0801 minio/*py; fi @if python --version | grep -qi 'python 3'; then pylint --reports=no --score=no minio/credentials minio/select tests/functional; fi @isort --diff --recursive . diff --git a/docs/API.md b/docs/API.md index 5a6da598e..7788a7ea1 100644 --- a/docs/API.md +++ b/docs/API.md @@ -30,21 +30,21 @@ s3Client = Minio( ) ``` -| Bucket operations | Object operations | Presigned operations | Bucket policy/notification/encryption operations | -|:----------------------------------------------------------|:--------------------------------------------------|:--------------------------------------------------|:--------------------------------------------------------------------| -| [`make_bucket`](#make_bucket) | [`get_object`](#get_object) | [`presigned_get_object`](#presigned_get_object) | [`get_bucket_policy`](#get_bucket_policy) | -| [`list_buckets`](#list_buckets) | [`put_object`](#put_object) | [`presigned_put_object`](#presigned_put_object) | [`set_bucket_policy`](#set_bucket_policy) | -| [`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) | -| [`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) | -| [`delete_bucket_replication`](#delete_bucket_replication) | [`fget_object`](#fget_object) | | [`get_bucket_encryption`](#get_bucket_encryption) | -| [`get_bucket_replication`](#get_bucket_replication) | [`select_object_content`](#select_object_content) | | [`remove_all_bucket_notification`](#remove_all_bucket_notification) | -| [`set_bucket_replication`](#set_bucket_replication) | | | [`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) | | | | +| Bucket operations | Object operations | Presigned operations | Bucket policy/notification/encryption operations | +|:----------------------------------------------------------|:----------------------------------------------------------------|:--------------------------------------------------|:--------------------------------------------------------------------| +| [`make_bucket`](#make_bucket) | [`get_object`](#get_object) | [`presigned_get_object`](#presigned_get_object) | [`get_bucket_policy`](#get_bucket_policy) | +| [`list_buckets`](#list_buckets) | [`put_object`](#put_object) | [`presigned_put_object`](#presigned_put_object) | [`set_bucket_policy`](#set_bucket_policy) | +| [`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) | +| [`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) | +| [`delete_bucket_replication`](#delete_bucket_replication) | [`fget_object`](#fget_object) | | [`get_bucket_encryption`](#get_bucket_encryption) | +| [`get_bucket_replication`](#get_bucket_replication) | [`select_object_content`](#select_object_content) | | [`remove_all_bucket_notification`](#remove_all_bucket_notification) | +| [`set_bucket_replication`](#set_bucket_replication) | [`enable_object_legal_hold`](#enable_object_legal_hold) | | [`put_bucket_encryption`](#put_bucket_encryption) | +| [`delete_bucket_lifecycle`](#delete_bucket_lifecycle) | [`disable_object_legal_hold`](#disable_object_legal_hold) | | [`delete_bucket_encryption`](#delete_bucket_encryption) | +| [`get_bucket_lifecycle`](#get_bucket_lifecycle) | [`is_object_legal_hold_enabled`](#is_object_legal_hold_enabled) | | | +| [`set_bucket_lifecycle`](#set_bucket_lifecycle) | | | | ## 1. Constructor @@ -1112,6 +1112,66 @@ minio.remove_objects( ) ``` + + +### enable_object_legal_hold(bucket_name, object_name, version_id=None) + +Enable legal hold on 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.enable_object_legal_hold("my-bucketname", "my-objectname") +``` + + + +### disable_object_legal_hold(bucket_name, object_name, version_id=None) + +Disable legal hold on 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.disable_object_legal_hold("my-bucketname", "my-objectname") +``` + + + +### is_object_legal_hold_enabled(bucket_name, object_name, version_id=None) + +Returns true if legal hold is enabled on 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.is_object_legal_hold_enabled("my-bucketname", "my-objectname") +``` + ## 4. Presigned operations diff --git a/examples/disable_object_legal_hold.py b/examples/disable_object_legal_hold.py new file mode 100644 index 000000000..70db87abf --- /dev/null +++ b/examples/disable_object_legal_hold.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.disable_object_legal_hold("my-bucketname", "my-objectname") diff --git a/examples/enable_object_legal_hold.py b/examples/enable_object_legal_hold.py new file mode 100644 index 000000000..fd77afc70 --- /dev/null +++ b/examples/enable_object_legal_hold.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.enable_object_legal_hold("my-bucketname", "my-objectname") diff --git a/examples/is_object_legal_hold_enabled.py b/examples/is_object_legal_hold_enabled.py new file mode 100644 index 000000000..057f477c2 --- /dev/null +++ b/examples/is_object_legal_hold_enabled.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", +) + +status = client.is_object_legal_hold_enabled("my-bucketname", "my-objectname") diff --git a/minio/api.py b/minio/api.py index c01cfbf45..bf8418a5e 100644 --- a/minio/api.py +++ b/minio/api.py @@ -52,6 +52,7 @@ is_supported_header, is_valid_notification_config, is_valid_policy_type, makedirs, md5sum_hash, quote, read_part_data, sha256_hash, strptime_rfc3339) +from .legalhold import LegalHold from .lifecycleconfig import LifecycleConfig from .parsers import (parse_error_response, parse_get_bucket_notification, parse_list_buckets, parse_list_multipart_uploads, @@ -1924,6 +1925,93 @@ def set_bucket_lifecycle(self, bucket_name, config): query_params={"lifecycle": ""}, ) + def enable_object_legal_hold( + self, bucket_name, object_name, version_id=None, + ): + """ + Enable legal hold on 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.enable_object_legal_hold("my-bucketname", "my-objectname") + """ + check_bucket_name(bucket_name) + check_non_empty_string(object_name) + body = marshal(LegalHold(True)) + query_params = {"versionId", version_id} if version_id else {} + query_params["legal-hold"] = "" + self._execute( + "PUT", + bucket_name, + object_name=object_name, + body=body, + headers={"Content-MD5": md5sum_hash(body)}, + query_params=query_params, + ) + + def disable_object_legal_hold( + self, bucket_name, object_name, version_id=None, + ): + """ + Disable legal hold on 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.disable_object_legal_hold("my-bucketname", "my-objectname") + """ + check_bucket_name(bucket_name) + check_non_empty_string(object_name) + body = marshal(LegalHold(False)) + query_params = {"versionId", version_id} if version_id else {} + query_params["legal-hold"] = "" + self._execute( + "PUT", + bucket_name, + object_name=object_name, + body=body, + headers={"Content-MD5": md5sum_hash(body)}, + query_params=query_params, + ) + + def is_object_legal_hold_enabled( + self, bucket_name, object_name, version_id=None, + ): + """ + Returns true if legal hold is enabled on 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:: + status = minio.is_object_legal_hold_enabled( + "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["legal-hold"] = "" + try: + response = self._execute( + "GET", + bucket_name, + object_name=object_name, + query_params=query_params, + ) + legal_hold = unmarshal(LegalHold, response.data.decode()) + return legal_hold.status + except S3Error as exc: + if exc.code != "NoSuchObjectLockConfiguration": + raise + return False + def _list_objects( # pylint: disable=too-many-arguments,too-many-branches self, bucket_name, diff --git a/minio/legalhold.py b/minio/legalhold.py new file mode 100644 index 000000000..c5b23315a --- /dev/null +++ b/minio/legalhold.py @@ -0,0 +1,45 @@ +# -*- 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. + +"""Request/response of PutObjectLegalHold and GetObjectLegalHold S3 APIs.""" + +from __future__ import absolute_import + +from .xml import Element, SubElement, findtext + + +class LegalHold: + """Legal hold configuration.""" + + def __init__(self, status=False): + self._status = status + + @property + def status(self): + """Get status.""" + return self._status + + @classmethod + def fromxml(cls, element): + """Create new object with values from XML element.""" + status = findtext(element, "Status") + return cls(status == "ON") + + def toxml(self, element): + """Convert to XML.""" + element = Element("LegalHold") + SubElement(element, "Status", "ON" if self._status is True else "OFF") + return element diff --git a/tests/unit/legelhold.py b/tests/unit/legelhold.py new file mode 100644 index 000000000..9382a4976 --- /dev/null +++ b/tests/unit/legelhold.py @@ -0,0 +1,37 @@ +# -*- 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 nose.tools import eq_ + +from minio import xml +from minio.legalhold import LegalHold + + +class LegalHoldTest(TestCase): + def test_status(self): + config = LegalHold(True) + xml.marshal(config) + + config = xml.unmarshal( + LegalHold, + """ + OFF +""", + ) + xml.marshal(config) + eq_(config.status, False)