diff --git a/README.md b/README.md
index 1420e2ec..877421c3 100644
--- a/README.md
+++ b/README.md
@@ -210,6 +210,8 @@ The full API Reference is available here.
* [put-object-tagging.js](https://github.com/minio/minio-js/blob/master/examples/put-object-tagging.js)
* [get-object-tagging.js](https://github.com/minio/minio-js/blob/master/examples/get-object-tagging.js)
* [remove-object-tagging.js](https://github.com/minio/minio-js/blob/master/examples/remove-object-tagging.js)
+* [set-object-legal-hold.js](https://github.com/minio/minio-js/blob/master/examples/set-object-legalhold.js)
+* [get-object-legal-hold.js](https://github.com/minio/minio-js/blob/master/examples/get-object-legal-hold.js)
#### Full Examples : Presigned Operations
* [presigned-getobject.js](https://github.com/minio/minio-js/blob/master/examples/presigned-getobject.js)
diff --git a/docs/API.md b/docs/API.md
index c3505159..a61ba10b 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -44,17 +44,18 @@ var s3Client = new Minio.Client({
| [`setBucketLifecycle`](#setBucketLifecycle) | [`putObjectTagging`](#putObjectTagging) |
| [`getBucketLifecycle`](#getBucketLifecycle) | [`removeObjectTagging`](#removeObjectTagging) |
| [`removeBucketLifecycle`](#removeBucketLifecycle) | [`getObjectTagging`](#getObjectTagging) |
-| [`setObjectLockConfig`](#setObjectLockConfig) | [`putObjectRetention`](#putObjectRetention) |
-| [`getObjectLockConfig`](#getObjectLockConfig) | [`getObjectRetention`](#getObjectRetention) |
-|[`setBucketEncryption`](#setBucketEncryption) | |
-|[`getBucketEncryption`](#getBucketEncryption) | |
-|[`removeBucketEncryption`](#removeBucketEncryption) | |
-| [`removeBucketLifecycle`](#removeBucketLifecycle) | |
+| [`setObjectLockConfig`](#setObjectLockConfig) | [`getObjectLegalHold`](#getObjectLegalHold) |
+| [`getBucketEncryption`](#getBucketEncryption) | [`setObjectLegalHold`](#setObjectLegalHold) |
+| [`getObjectLockConfig`](#getObjectLockConfig) | [`getObjectLegalHold`](#getObjectLegalHold) |
+| [`getBucketEncryption`](#getBucketEncryption) | [`setObjectLegalHold`](#setObjectLegalHold) |
+| [`setBucketEncryption`](#setBucketEncryption) | |
+| [`removeBucketEncryption`](#removeBucketEncryption) | |
| [`setBucketReplication`](#setBucketReplication)| |
| [`getBucketReplication`](#getBucketReplication)| |
| [`removeBucketReplication`](#removeBucketReplication)| |
-
-
+| [`setBucketEncryption`](#setBucketEncryption) | |
+| [`getBucketEncryption`](#getBucketEncryption) | |
+| [`removeBucketEncryption`](#removeBucketEncryption) | |
## 1. Constructor
@@ -1590,6 +1591,89 @@ minioClient.getObjectTagging('bucketname', 'object-name', {versionId:"my-object-
})
```
+
+### getObjectLegalHold(bucketName, objectName, getOpts [, callback])
+
+Get legal hold on an object.
+
+__Parameters__
+
+
+| Param | Type | Description |
+|---|---|---|
+| `bucketName` |_string_ | Name of the bucket. |
+| `objectName` | _string_ | Name of the object. |
+| `getOpts` | _object_ | Legal hold configuration options. e.g `{versionId:'my-version-uuid'}` defaults to `{}` . |
+| `callback(err)` | _function_ |Callback function is called with non `null` value in case of error. If no callback is passed, a `Promise` is returned. |
+
+
+__Example 1__
+
+Get Legal hold of an object.
+
+```js
+minioClient.getObjectLegalHold('bucketName', 'objectName', {}, function(err, res) {
+ if (err) {
+ return console.log('Unable to get legal hold config for the object', err.message)
+ }
+ console.log('Success', res)
+})
+```
+
+__Example 2__
+
+Get Legal hold of an object with versionId.
+
+```js
+minioClient.getObjectLegalHold('bucketName', 'objectName', { versionId:'my-obj-version-uuid' }, function(err, res) {
+ if (err) {
+ return console.log('Unable to get legal hold config for the object', err.message)
+ }
+ console.log('Success', res)
+})
+```
+
+
+
+### setObjectLegalHold(bucketName, objectName, [,setOpts, callback])
+
+Set legal hold on an object.
+
+__Parameters__
+
+
+| Param | Type | Description |
+|---|---|---|
+| `bucketName` |_string_ | Name of the bucket. |
+| `objectName` | _string_ | Name of the object. |
+| `setOpts` | _object_ | Legal hold configuration options to set. e.g `{versionId:'my-version-uuid', status:'ON or OFF'}` defaults to `{status:'ON'}` if not passed. |
+| `callback(err)` | _function_ |Callback function is called with non `null` value in case of error. If no callback is passed, a `Promise` is returned. |
+
+
+__Example 1__
+
+Set Legal hold of an object.
+```js
+minioClient.setObjectLegalHold('bucketName', 'objectName', {Status:"ON"}, function(err, res) {
+ if (err) {
+ return console.log('Unable to set legal hold config for the object', err.message)
+ }
+ console.log('Success')
+})
+```
+
+__Example 2__
+
+Set Legal hold of an object with versionId.
+```js
+minioClient.setObjectLegalHold('bucketName', 'objectName', { Status:"ON", versionId:'my-obj-version-uuid' }, function(err, res) {
+ if (err) {
+ return console.log('Unable to set legal hold config for the object version', err.message)
+ }
+ console.log('Success')
+})
+```
+
## 4. Presigned operations
Presigned URLs are generated for temporary download/upload access to private objects.
diff --git a/examples/get-object-legal-hold.js b/examples/get-object-legal-hold.js
new file mode 100644
index 00000000..1e42a14b
--- /dev/null
+++ b/examples/get-object-legal-hold.js
@@ -0,0 +1,54 @@
+/*
+ * MinIO Javascript Library for Amazon S3 Compatible Cloud Storage, (C) 2021 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.
+
+var Minio = require('minio')
+
+var s3Client = new Minio.Client({
+ endPoint: 's3.amazonaws.com',
+ accessKey: 'YOUR-ACCESSKEYID',
+ secretKey: 'YOUR-SECRETACCESSKEY'
+})
+
+//Get Legalhold config
+s3Client.getObjectLegalHold('bucketName', 'objectName', {}, function(err, res) {
+ if (err) {
+ return console.log('Unable to get legal hold config for the object', err.message) // Print only the message.
+ }
+ console.log(res)
+})
+
+//With versionId
+s3Client.getObjectLegalHold('bucketName', 'objectName', { versionId:'my-obj-version-uuid' }, function(err, res) {
+ if (err) {
+ return console.log('Unable to get legal hold config for the object', err.message) // Print only the message.
+ }
+ console.log(res)
+})
+
+//Promise based version:
+const objectLegalHoldPromise = s3Client.getObjectLegalHold('bucketName', 'objectName', { versionId:'my-obj-version-uuid' })
+objectLegalHoldPromise.then((data) => {
+ console.log("Success...", data)
+})
+ .catch((e)=>{
+ // Print only the error message. if called on an object without object lock config.
+ // e.g: "The specified object does not have a ObjectLock configuration"
+ console.log(e.message)
+
+ })
diff --git a/examples/set-object-legal-hold.js b/examples/set-object-legal-hold.js
new file mode 100644
index 00000000..0320639c
--- /dev/null
+++ b/examples/set-object-legal-hold.js
@@ -0,0 +1,42 @@
+/*
+ * MinIO Javascript Library for Amazon S3 Compatible Cloud Storage, (C) 2021 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.
+
+var Minio = require('minio')
+
+var s3Client = new Minio.Client({
+ endPoint: 's3.amazonaws.com',
+ accessKey: 'YOUR-ACCESSKEYID',
+ secretKey: 'YOUR-SECRETACCESSKEY'
+})
+
+//Set legal hold config of an object.
+s3Client.setObjectLegalHold('bucketName', 'objectName', {status:"ON"}, function(err, res) {
+ if (err) {
+ return console.log('Unable to set legal hold config for the object', err)
+ }
+ console.log('Success')
+})
+
+//Set legal hold config of an object with versionId.
+s3Client.setObjectLegalHold('bucketName', 'objectName', { status:"ON", versionId:'my-obj-version-uuid' }, function(err, res) {
+ if (err) {
+ return console.log('Unable to set legal hold config for the object version', err)
+ }
+ console.log('Success')
+})
\ No newline at end of file
diff --git a/src/main/helpers.js b/src/main/helpers.js
index 0bdedc4d..56ae2d4d 100644
--- a/src/main/helpers.js
+++ b/src/main/helpers.js
@@ -379,3 +379,8 @@ export const RETENTION_VALIDITY_UNITS={
DAYS:"Days",
YEARS:"Years"
}
+
+export const LEGAL_HOLD_STATUS={
+ ENABLED:"ON",
+ DISABLED:"OFF"
+}
\ No newline at end of file
diff --git a/src/main/minio.js b/src/main/minio.js
index caea391e..6bf1b368 100644
--- a/src/main/minio.js
+++ b/src/main/minio.js
@@ -36,7 +36,7 @@ import {
isString, isObject, isArray, isValidDate, pipesetup,
readableStream, isReadableStream, isVirtualHostStyle,
insertContentType, makeDateLong, promisify, getVersionId, sanitizeETag,
- RETENTION_MODES, RETENTION_VALIDITY_UNITS
+ RETENTION_MODES, RETENTION_VALIDITY_UNITS, LEGAL_HOLD_STATUS
} from './helpers.js'
import { signV4, presignSignatureV4, postPresignSignatureV4 } from './signing.js'
@@ -2793,22 +2793,23 @@ export class Client {
if(!_.isEmpty(encryptionConfig) && encryptionConfig.Rule.length >1){
throw new errors.InvalidArgumentError('Invalid Rule length. Only one rule is allowed.: ' + encryptionConfig.Rule)
}
-
- if (!isFunction(cb)) {
+ if (cb && !isFunction(cb)) {
throw new TypeError('callback should be of type "function"')
}
- const encryptionObj = _.isEmpty(encryptionConfig) ? {
+ let encryptionObj =encryptionConfig
+ if(_.isEmpty(encryptionConfig)) {
+ encryptionObj={
//Default MinIO Server Supported Rule
- Rule:[
- {
- ApplyServerSideEncryptionByDefault: {
- SSEAlgorithm:"AES256"
+ Rule:[
+ {
+ ApplyServerSideEncryptionByDefault: {
+ SSEAlgorithm:"AES256"
+ }
}
- }
- ]
+ ]
- } : encryptionConfig
+ }}
let method = 'PUT'
let query = "encryption"
@@ -2935,6 +2936,114 @@ export class Client {
this.makeRequest({method, bucketName, query}, '', 200, '', false, cb)
}
+
+ getObjectLegalHold(bucketName, objectName, getOpts={}, cb){
+ if (!isValidBucketName(bucketName)) {
+ throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
+ }
+ if (!isValidObjectName(objectName)) {
+ throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`)
+ }
+
+ if(isFunction(getOpts)){
+ cb= getOpts
+ getOpts = {}
+ }
+
+ if (!isObject(getOpts)) {
+ throw new TypeError('getOpts should be of type "Object"')
+ } else if(Object.keys(getOpts).length> 0 && getOpts.versionId && !isString((getOpts.versionId))){
+ throw new TypeError('versionId should be of type string.:',getOpts.versionId )
+ }
+
+
+ if (!isFunction(cb)) {
+ throw new errors.InvalidArgumentError('callback should be of type "function"')
+ }
+
+ const method = 'GET'
+ let query = "legal-hold"
+
+ if (getOpts.versionId){
+ query +=`&versionId=${getOpts.versionId}`
+ }
+
+ this.makeRequest({method, bucketName, objectName, query}, '', 200, '', true, (e, response) => {
+ if (e) return cb(e)
+
+ let legalHoldConfig = Buffer.from('')
+ pipesetup(response, transformers.objectLegalHoldTransformer())
+ .on('data', data => {
+ legalHoldConfig = data
+ })
+ .on('error', cb)
+ .on('end', () => {
+ cb(null, legalHoldConfig)
+ })
+ })
+
+ }
+
+ setObjectLegalHold(bucketName, objectName, setOpts={}, cb){
+ if (!isValidBucketName(bucketName)) {
+ throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
+ }
+ if (!isValidObjectName(objectName)) {
+ throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`)
+ }
+
+ const defaultOpts = {
+ status:LEGAL_HOLD_STATUS.ENABLED
+ }
+ if(isFunction(setOpts)){
+ cb= setOpts
+ setOpts =defaultOpts
+ }
+
+ if (!isObject(setOpts)) {
+ throw new TypeError('setOpts should be of type "Object"')
+ }else {
+
+ if(![LEGAL_HOLD_STATUS.ENABLED, LEGAL_HOLD_STATUS.DISABLED].includes((setOpts.status))){
+ throw new TypeError('Invalid status: '+setOpts.status )
+ }
+ if(setOpts.versionId && !setOpts.versionId.length){
+ throw new TypeError('versionId should be of type string.:'+ setOpts.versionId )
+ }
+ }
+
+ if (!isFunction(cb)) {
+ throw new errors.InvalidArgumentError('callback should be of type "function"')
+ }
+
+ if( _.isEmpty(setOpts)){
+ setOpts={
+ defaultOpts
+ }
+ }
+
+ const method = 'PUT'
+ let query = "legal-hold"
+
+ if (setOpts.versionId){
+ query +=`&versionId=${setOpts.versionId}`
+ }
+
+ let config={
+ Status: setOpts.status
+ }
+
+ const builder = new xml2js.Builder({rootName:'LegalHold', renderOpts:{'pretty':false}, headless:true})
+ const payload = builder.buildObject(config)
+ const headers = {}
+ const md5digest = Crypto.createHash('md5').update(payload).digest()
+ headers['Content-MD5'] = md5digest.toString('base64')
+
+ this.makeRequest({method, bucketName, objectName, query, headers}, payload, 200, '', false, cb)
+
+
+ }
+
get extensions() {
if(!this.clientExtensions)
{
@@ -2991,6 +3100,8 @@ Client.prototype.removeBucketEncryption = promisify(Client.prototype.removeBucke
Client.prototype.setBucketReplication =promisify(Client.prototype.setBucketReplication)
Client.prototype.getBucketReplication =promisify(Client.prototype.getBucketReplication)
Client.prototype.removeBucketReplication=promisify(Client.prototype.removeBucketReplication)
+Client.prototype.setObjectLegalHold=promisify(Client.prototype.setObjectLegalHold)
+Client.prototype.getObjectLegalHold=promisify(Client.prototype.getObjectLegalHold)
export class CopyConditions {
constructor() {
diff --git a/src/main/transformers.js b/src/main/transformers.js
index a84f87d0..8b5b7df6 100644
--- a/src/main/transformers.js
+++ b/src/main/transformers.js
@@ -240,4 +240,8 @@ export function bucketEncryptionTransformer(){
export function replicationConfigTransformer(){
return getConcater(xmlParsers.parseReplicationConfig)
+}
+
+export function objectLegalHoldTransformer(){
+ return getConcater(xmlParsers.parseObjectLegalHoldConfig)
}
\ No newline at end of file
diff --git a/src/main/xml-parsers.js b/src/main/xml-parsers.js
index 8734b163..ae303ce2 100644
--- a/src/main/xml-parsers.js
+++ b/src/main/xml-parsers.js
@@ -536,4 +536,9 @@ export function parseReplicationConfig(xml){
}
}
return replicationConfig
+}
+
+export function parseObjectLegalHoldConfig(xml){
+ const xmlObj = parseXml(xml)
+ return xmlObj.LegalHold
}
\ No newline at end of file
diff --git a/src/test/functional/functional-tests.js b/src/test/functional/functional-tests.js
index e476bbd4..707ed034 100644
--- a/src/test/functional/functional-tests.js
+++ b/src/test/functional/functional-tests.js
@@ -2374,4 +2374,120 @@ describe('functional tests', function() {
//https://docs.min.io/minio/baremetal/replication/replication-overview.html#minio-bucket-replication-clientside
})
+ describe('Object Legal hold API Tests', ()=>{
+ //Isolate the bucket/object for easy debugging and tracking.
+ //Gateway mode does not support this header.
+ let versionId = null
+ describe('Object Legal hold get/set API Test', function () {
+ const objLegalHoldBucketName = "minio-js-test-legalhold-" + uuid.v4()
+ const objLegalHoldObjName = "LegalHoldObject"
+ let isFeatureSupported = false
+
+
+ step(`Check if bucket with object lock can be created:_bucketName:${objLegalHoldBucketName}`, done => {
+ client.makeBucket(objLegalHoldBucketName, {ObjectLocking: true}, (err) => {
+ if (err && err.code === 'NotImplemented') return done()
+ isFeatureSupported = true
+ if (err) return done(err)
+ done()
+ })
+ })
+
+ step(`putObject(bucketName, objectName, stream)_bucketName:${objLegalHoldBucketName}, objectName:${objLegalHoldObjName}, stream:100Kib_`, done => {
+ if (isFeatureSupported) {
+ client.putObject(objLegalHoldBucketName, objLegalHoldObjName, readableStream(_1byte), _1byte.length, {})
+ .then(() => done())
+ .catch(done)
+ } else {
+ done()
+ }
+ })
+
+ step(`statObject(bucketName, objectName, statOpts)_bucketName:${objLegalHoldBucketName}, objectName:${objLegalHoldObjName}`, done => {
+ if (isFeatureSupported) {
+ client.statObject(objLegalHoldBucketName, objLegalHoldObjName, {}, (e, res) => {
+ versionId = res.versionId
+ done()
+ })
+ } else {
+ done()
+ }
+ })
+
+ step(`setObjectLegalHold(bucketName, objectName, setOpts={})_bucketName:${objLegalHoldBucketName}, objectName:${objLegalHoldObjName}`, done => {
+ if (isFeatureSupported) {
+ client.setObjectLegalHold(objLegalHoldBucketName, objLegalHoldObjName, () => {
+ done()
+ })
+ } else {
+ done()
+ }
+ })
+
+ step(`setObjectLegalHold(bucketName, objectName, setOpts={})_bucketName:${objLegalHoldBucketName}, objectName:${objLegalHoldObjName}`, done => {
+ if (isFeatureSupported) {
+ client.setObjectLegalHold(objLegalHoldBucketName, objLegalHoldObjName, {status:"ON", versionId:versionId}, () => {
+ done()
+ })
+ } else {
+ done()
+ }
+
+ })
+
+ step(`getObjectLegalHold(bucketName, objectName, setOpts={})_bucketName:${objLegalHoldBucketName}, objectName:${objLegalHoldObjName}`, done => {
+ if (isFeatureSupported) {
+ client.getObjectLegalHold(objLegalHoldBucketName, objLegalHoldObjName, () => {
+ done()
+ })
+ } else {
+ done()
+ }
+ })
+
+ step(`setObjectLegalHold(bucketName, objectName, setOpts={})_bucketName:${objLegalHoldBucketName}, objectName:${objLegalHoldObjName}`, done => {
+ if (isFeatureSupported) {
+ client.setObjectLegalHold(objLegalHoldBucketName, objLegalHoldObjName, {status:"OFF", versionId:versionId}, () => {
+ done()
+ })
+ } else {
+ done()
+ }
+
+ })
+
+ step(`getObjectLegalHold(bucketName, objectName, setOpts={})_bucketName:${objLegalHoldBucketName}, objectName:${objLegalHoldObjName}`, done => {
+ if (isFeatureSupported) {
+ client.getObjectLegalHold(objLegalHoldBucketName, objLegalHoldObjName, {versionId:versionId}, () => {
+ done()
+ })
+ } else {
+ done()
+ }
+
+ })
+
+ step(`removeObject(bucketName, objectName, removeOpts)_bucketName:${objLegalHoldBucketName}, objectName:${objLegalHoldObjName}`, done => {
+ if(isFeatureSupported) {
+ client.removeObject(objLegalHoldBucketName, objLegalHoldObjName, {versionId:versionId, governanceBypass:true}, () => {
+ done()
+ })
+ }else{
+ done()
+ }
+
+ })
+
+ step(`removeBucket(bucketName, )_bucketName:${objLegalHoldBucketName}`, done => {
+ if(isFeatureSupported) {
+ client.removeBucket(objLegalHoldBucketName, () => {
+ done()
+ })
+ }else{
+ done()
+ }
+
+ })
+
+ })})
})
diff --git a/src/test/unit/test.js b/src/test/unit/test.js
index 92f2f441..1f480041 100644
--- a/src/test/unit/test.js
+++ b/src/test/unit/test.js
@@ -1342,5 +1342,90 @@ describe('Client', function() {
})
})
+
+ describe('Object Legal Hold APIs', ()=> {
+ describe('getObjectLegalHold(bucketName, objectName, getOpts={}, cb)', () => {
+ it('should fail on null bucket', (done) => {
+ try {
+ client.getObjectLegalHold(null, function () {
+ })
+ } catch (e) {
+ done()
+ }
+ })
+ it('should fail on empty bucket', (done) => {
+ try {
+ client.getObjectLegalHold('', function () {
+ })
+ } catch (e) {
+ done()
+ }
+ })
+
+ it('should fail on null objectName', (done) => {
+ try {
+ client.getObjectLegalHold('my-bucket', null, function () {
+ })
+ } catch (e) {
+ done()
+ }
+ })
+ it('should fail on null getOpts', (done) => {
+ try {
+ client.getObjectLegalHold('my-bucker', 'my-object', null, function () {
+ })
+ } catch (e) {
+ done()
+ }
+ })
+ })
+
+ describe('setObjectLegalHold(bucketName, objectName, setOpts={}, cb)', () => {
+ it('should fail on null bucket', (done) => {
+ try {
+ client.setObjectLegalHold(null, function () {
+ })
+ } catch (e) {
+ done()
+ }
+ })
+ it('should fail on empty bucket', (done) => {
+ try {
+ client.setObjectLegalHold('', function () {
+ })
+ } catch (e) {
+ done()
+ }
+ })
+
+ it('should fail on null objectName', (done) => {
+ try {
+ client.setObjectLegalHold('my-bucket', null, function () {
+ })
+ } catch (e) {
+ done()
+ }
+ })
+ it('should fail on null setOpts', (done) => {
+ try {
+ client.setObjectLegalHold('my-bucker', 'my-object', null, function () {
+ })
+ } catch (e) {
+ done()
+ }
+ })
+ it('should fail on empty versionId', (done) => {
+ try {
+ client.setObjectLegalHold('my-bucker', 'my-object', {}, function () {
+ })
+ } catch (e) {
+ done()
+ }
+ })
+ })
+ })
+
+
+
})