Repository: minio/minio-py
Pull request #985 (2020-10-02 05:03:14)

add {set,get,delete}_object_lock_config APIs

kannappanr: do we need to add functional tests for these APIs?
balamurugana: > do we need to add functional tests for these APIs?

Many existing APIs do not have functional tests. Not sure whether tests are needed for these APIs.

Patch:
diff --git a/docs/API.md b/docs/API.md
index eca825a..5780a7d 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -58,6 +58,9 @@ s3Client = Minio(
 | [`delete_bucket_encryption`](#delete_bucket_encryption)     |                                                                 |                                                   |
 | [`get_bucket_encryption`](#get_bucket_encryption)           |                                                                 |                                                   |
 | [`put_bucket_encryption`](#put_bucket_encryption)           |                                                                 |                                                   |
+| [`delete_object_lock_config`](#delete_object_lock_config)   |                                                                 |                                                   |
+| [`get_object_lock_config`](#get_object_lock_config)         |                                                                 |                                                   |
+| [`set_object_lock_config`](#set_object_lock_config)         |                                                                 |                                                   |
 
 ## 1. Constructor
 
@@ -763,6 +766,66 @@ tags["User"] = "jsmith"
 client.set_bucket_tags("my-bucketname", tags)
 ```
 
+<a name="delete_object_lock_config"></a>
+
+### delete_object_lock_config(bucket_name)
+
+Delete object-lock configuration of a bucket.
+
+__Parameters__
+
+| Param           | Type  | Description         |
+|:----------------|:------|:--------------------|
+| ``bucket_name`` | _str_ | Name of the bucket. |
+
+__Example__
+
+```py
+minio.delete_object_lock_config("my-bucketname")
+```
+
+<a name="get_object_lock_config"></a>
+
+### get_object_lock_config(bucket_name)
+
+Get object-lock configuration of a bucket.
+
+__Parameters__
+
+| Param           | Type  | Description         |
+|:----------------|:------|:--------------------|
+| ``bucket_name`` | _str_ | Name of the bucket. |
+
+| Return                     |
+|:---------------------------|
+| _ObjectLockConfig_ object. |
+
+__Example__
+
+```py
+config = minio.get_object_lock_config("my-bucketname")
+```
+
+<a name="set_object_lock_config"></a>
+
+### set_object_lock_config(bucket_name, config)
+
+Set object-lock configuration to a bucket.
+
+__Parameters__
+
+| Param           | Type               | Description                |
+|:----------------|:-------------------|:---------------------------|
+| ``bucket_name`` | _str_              | Name of the bucket.        |
+| ``config``      | _ObjectLockConfig_ | Object-Lock configuration. |
+
+__Example__
+
+```py
+config = ObjectLockConfig(GOVERNANCE, 15, DAYS)
+minio.set_object_lock_config("my-bucketname", config)
+```
+
 ## 3. Object operations
 
 <a name="get_object"></a>
diff --git a/examples/delete_object_lock_config.py b/examples/delete_object_lock_config.py
new file mode 100644
index 0000000..9561afa
--- /dev/null
+++ b/examples/delete_object_lock_config.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_lock_config("my-bucketname")
diff --git a/examples/get_object_lock_config.py b/examples/get_object_lock_config.py
new file mode 100644
index 0000000..5d98678
--- /dev/null
+++ b/examples/get_object_lock_config.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",
+)
+
+config = client.get_object_lock_config("my-bucketname")
diff --git a/examples/set_object_lock_config.py b/examples/set_object_lock_config.py
new file mode 100644
index 0000000..bf2ce4f
--- /dev/null
+++ b/examples/set_object_lock_config.py
@@ -0,0 +1,31 @@
+# -*- 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 GOVERNANCE
+from minio.objectlockconfig import DAYS, ObjectLockConfig
+
+client = Minio(
+    "play.min.io",
+    access_key="Q3AM3UQ867SPQQA43P2F",
+    secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG",
+)
+
+config = ObjectLockConfig(GOVERNANCE, 15, DAYS)
+client.set_object_lock_condig("my-bucketname", config)
diff --git a/minio/api.py b/minio/api.py
index 3fe64d0..bf8692c 100644
--- a/minio/api.py
+++ b/minio/api.py
@@ -58,6 +58,7 @@ from .helpers import (amzprefix_user_metadata, check_bucket_name,
 from .legalhold import LegalHold
 from .lifecycleconfig import LifecycleConfig
 from .notificationconfig import NotificationConfig
+from .objectlockconfig import ObjectLockConfig
 from .parsers import (parse_error_response,
                       parse_list_multipart_uploads,
                       parse_list_parts,
@@ -2182,6 +2183,58 @@ class Minio:  # pylint: disable=too-many-public-methods
                 raise
         return False
 
+    def delete_object_lock_config(self, bucket_name):
+        """
+        Delete object-lock configuration of a bucket.
+
+        :param bucket_name: Name of the bucket.
+
+        Example::
+            minio.delete_object_lock_config("my-bucketname")
+        """
+        self.set_object_lock_config(
+            self, bucket_name, ObjectLockConfig(None, None, None),
+        )
+
+    def get_object_lock_config(self, bucket_name):
+        """
+        Get object-lock configuration of a bucket.
+
+        :param bucket_name: Name of the bucket.
+        :return: :class:`ObjectLockConfig <ObjectLockConfig>` object.
+
+        Example::
+            config = minio.get_object_lock_config("my-bucketname")
+        """
+        check_bucket_name(bucket_name)
+        response = self._execute(
+            "GET", bucket_name, query_params={"object-lock": ""},
+        )
+        return unmarshal(ObjectLockConfig, response.data.decode())
+
+    def set_object_lock_config(self, bucket_name, config):
+        """
+        Set object-lock configuration to a bucket.
+
+        :param bucket_name: Name of the bucket.
+        :param config: :class:`ObjectLockConfig <ObjectLockConfig>` object.
+
+        Example::
+            config = ObjectLockConfig(GOVERNANCE, 15, DAYS)
+            minio.set_object_lock_config("my-bucketname", config)
+        """
+        check_bucket_name(bucket_name)
+        if not isinstance(config, ObjectLockConfig):
+            raise ValueError("config must be ObjectLockConfig type")
+        body = marshal(config)
+        self._execute(
+            "PUT",
+            bucket_name,
+            body=body,
+            headers={"Content-MD5": md5sum_hash(body)},
+            query_params={"object-lock": ""},
+        )
+
     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 85bf6d7..355aa37 100644
--- a/minio/commonconfig.py
+++ b/minio/commonconfig.py
@@ -25,6 +25,8 @@ from .xml import SubElement, find, findall, findtext
 
 DISABLED = "Disabled"
 ENABLED = "Enabled"
+GOVERNANCE = "GOVERNANCE"
+COMPLIANCE = "COMPLIANCE"
 _MAX_KEY_LENGTH = 128
 _MAX_VALUE_LENGTH = 256
 _MAX_OBJECT_TAG_COUNT = 10
diff --git a/minio/objectlockconfig.py b/minio/objectlockconfig.py
new file mode 100644
index 0000000..6540ffb
--- /dev/null
+++ b/minio/objectlockconfig.py
@@ -0,0 +1,88 @@
+# -*- 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 PutObjectLockConfiguration and GetObjectLockConfiguration
+APIs.
+"""
+
+from __future__ import absolute_import
+
+from .commonconfig import COMPLIANCE, ENABLED, GOVERNANCE
+from .xml import Element, SubElement, find, findtext
+
+DAYS = "Days"
+YEARS = "Years"
+
+
+class ObjectLockConfig:
+    """Object lock configuration."""
+
+    def __init__(self, mode, duration, duration_unit):
+        if (mode is not None) ^ (duration is not None):
+            if mode is None:
+                raise ValueError("mode must be provided")
+            raise ValueError("duration must be provided")
+        if mode is not None and mode not in [GOVERNANCE, COMPLIANCE]:
+            raise ValueError(
+                "mode must be {0} or {1}".format(GOVERNANCE, COMPLIANCE),
+            )
+        if duration_unit:
+            duration_unit = duration_unit.title()
+        if duration is not None and duration_unit not in [DAYS, YEARS]:
+            raise ValueError(
+                "duration unit must be {0} or {1}".format(DAYS, YEARS),
+            )
+        self._mode = mode
+        self._duration = duration
+        self._duration_unit = duration_unit
+
+    @property
+    def mode(self):
+        """Get mode."""
+        return self._mode
+
+    @property
+    def duration(self):
+        """Get duration and it's unit."""
+        return self._duration, self._duration_unit
+
+    @classmethod
+    def fromxml(cls, element):
+        """Create new object with values from XML element."""
+        element = find(element, "Rule")
+        if element is None:
+            return cls(None, None, None)
+        element = find(element, "DefaultRetention")
+        mode = findtext(element, "Mode")
+        duration_unit = DAYS
+        duration = findtext(element, duration_unit)
+        if not duration:
+            duration_unit = YEARS
+            duration = findtext(element, duration_unit)
+        duration = int(duration)
+        return cls(mode, duration, duration_unit)
+
+    def toxml(self, element):
+        """Convert to XML."""
+        element = Element("ObjectLockConfiguration")
+        SubElement(element, "ObjectLockEnabled", ENABLED)
+        if self._mode:
+            rule = SubElement(element, "Rule")
+            retention = SubElement(rule, "DefaultRetention")
+            SubElement(retention, "Mode", self._mode)
+            SubElement(retention, self._duration_unit, str(self._duration))
+        return element


Test patch:
diff --git a/tests/unit/objectlockconfig_test.py b/tests/unit/objectlockconfig_test.py
new file mode 100644
index 0000000..a621f09
--- /dev/null
+++ b/tests/unit/objectlockconfig_test.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.
+
+from unittest import TestCase
+
+from nose.tools import eq_
+
+from minio import xml
+from minio.commonconfig import COMPLIANCE, GOVERNANCE
+from minio.objectlockconfig import DAYS, YEARS, ObjectLockConfig
+
+
+class ObjectLockConfigTest(TestCase):
+    def test_config(self):
+        config = ObjectLockConfig(GOVERNANCE, 15, DAYS)
+        xml.marshal(config)
+
+        config = xml.unmarshal(
+            ObjectLockConfig,
+            """<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
+   <ObjectLockEnabled>Enabled</ObjectLockEnabled>
+   <Rule>
+      <DefaultRetention>
+         <Mode>COMPLIANCE</Mode>
+         <Years>3</Years>
+      </DefaultRetention>
+   </Rule>
+</ObjectLockConfiguration>""",
+        )
+        xml.marshal(config)
+        eq_(config.mode, COMPLIANCE)
+        eq_(config.duration, (3, YEARS))