Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor removeObjects api to ts #1285

Merged
merged 1 commit into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 15 additions & 27 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1158,18 +1158,17 @@ console.log(stat)

<a name="removeObject"></a>

### removeObject(bucketName, objectName [, removeOpts] [, callback])
### removeObject(bucketName, objectName [, removeOpts])

Removes an object.

**Parameters**

| Param | Type | Description |
| --------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `bucketName` | _string_ | Name of the bucket. |
| `objectName` | _string_ | Name of the object. |
| `removeOpts` | _object_ | Version of the object in the form `{versionId:"my-versionId", governanceBypass: true or false }`. Default is `{}`. (Optional) |
| `callback(err)` | _function_ | Callback function is called with non `null` value in case of error. If no callback is passed, a `Promise` is returned. |
| Param | Type | Description |
| ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `bucketName` | _string_ | Name of the bucket. |
| `objectName` | _string_ | Name of the object. |
| `removeOpts` | _object_ | Version of the object in the form `{versionId:"my-versionId", governanceBypass: true or false }`. Default is `{}`. (Optional) |

**Example 1**

Expand Down Expand Up @@ -1206,17 +1205,16 @@ Remove an object version locked with retention mode `GOVERNANCE` using the `gove

<a name="removeObjects"></a>

### removeObjects(bucketName, objectsList[, callback])
### removeObjects(bucketName, objectsList)

Remove all objects in the objectsList.

**Parameters**

| Param | Type | Description |
| --------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bucketName` | _string_ | Name of the bucket. |
| `objectsList` | _object_ | list of objects in the bucket to be removed. any one of the formats: 1. List of Object names as array of strings which are object keys: `['objectname1','objectname2']` 2. List of Object name and VersionId as an object: [{name:"my-obj-name",versionId:"my-versionId"}] |
| `callback(err)` | _function_ | Callback function is called with non `null` value in case of error. |
| Param | Type | Description |
| ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bucketName` | _string_ | Name of the bucket. |
| `objectsList` | _object_ | list of objects in the bucket to be removed. any one of the formats: 1. List of Object names as array of strings which are object keys: `['objectname1','objectname2']` 2. List of Object name and VersionId as an object: [{name:"my-obj-name",versionId:"my-versionId"}] |

**Example**

Expand All @@ -1234,13 +1232,8 @@ objectsStream.on('error', function (e) {
console.log(e)
})

objectsStream.on('end', function () {
s3Client.removeObjects('my-bucketname', objectsList, function (e) {
if (e) {
return console.log('Unable to remove Objects ', e)
}
console.log('Removed the objects successfully')
})
objectsStream.on('end', async () => {
await s3Client.removeObjects(bucket, objectsList)
})
```

Expand All @@ -1261,13 +1254,8 @@ objectsStream.on('data', function (obj) {
objectsStream.on('error', function (e) {
return console.log(e)
})
objectsStream.on('end', function () {
s3Client.removeObjects(bucket, objectsList, function (e) {
if (e) {
return console.log(e)
}
console.log('Success')
})
objectsStream.on('end', async () => {
await s3Client.removeObjects(bucket, objectsList)
})
```

Expand Down
21 changes: 5 additions & 16 deletions examples/remove-objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import * as Minio from 'minio'

const s3Client = new Minio.Client({
endPoint: 's3.amazonaws.com',
port: 9000,
useSSL: false,
accessKey: 'YOUR-ACCESSKEYID',
secretKey: 'YOUR-SECRETACCESSKEY',
})
Expand All @@ -48,32 +46,23 @@ function removeObjects(bucketName, prefix, recursive, includeVersion) {
return console.log(e)
})

objectsStream.on('end', function () {
s3Client.removeObjects(bucketName, objectsList, function (e) {
if (e) {
return console.log(e)
}
console.log('Success')
})
objectsStream.on('end', async () => {
const delRes = await s3Client.removeObjects(bucketName, objectsList)
console.log(delRes)
})
}

removeObjects(bucketName, prefix, recursive, true) // Versioned objects of a bucket to be deleted.
removeObjects(bucketName, prefix, recursive, false) // Normal objects of a bucket to be deleted.

// Delete Multiple objects and respective versions.
function removeObjectsMultipleVersions() {
async function removeObjectsMultipleVersions() {
const deleteList = [
{ versionId: '03ed08e1-34ff-4465-91ed-ba50c1e80f39', name: 'prefix-1/out.json.gz' },
{ versionId: '35517ae1-18cb-4a21-9551-867f53a10cfe', name: 'dir1/dir2/test.pdf' },
{ versionId: '3053f564-9aea-4a59-88f0-7f25d6320a2c', name: 'dir1/dir2/test.pdf' },
]

s3Client.removeObjects('my-bucket', deleteList, function (e) {
if (e) {
return console.log(e)
}
console.log('Successfully deleted..')
})
await s3Client.removeObjects('my-bucket', deleteList)
}
removeObjectsMultipleVersions()
60 changes: 42 additions & 18 deletions src/internal/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ import type {
ObjectRetentionInfo,
PutObjectLegalHoldOptions,
PutTaggingParams,
RemoveObjectsParam,
RemoveObjectsRequestEntry,
RemoveObjectsResponse,
RemoveTaggingParams,
ReplicationConfig,
ReplicationConfigOpts,
Expand All @@ -90,13 +93,13 @@ import type {
VersionIdentificator,
} from './type.ts'
import type { ListMultipartResult, UploadedPart } from './xml-parser.ts'
import * as xmlParsers from './xml-parser.ts'
import {
parseCompleteMultipart,
parseInitiateMultipart,
parseObjectLegalHoldConfig,
parseSelectObjectContentResponse,
} from './xml-parser.ts'
import * as xmlParsers from './xml-parser.ts'

const xml = new xml2js.Builder({ renderOpts: { pretty: false }, headless: true })

Expand Down Expand Up @@ -1111,42 +1114,30 @@ export class TypedClient {
}
}

/**
* Remove the specified object.
* @deprecated use new promise style API
*/
removeObject(bucketName: string, objectName: string, removeOpts: RemoveOptions, callback: NoResultCallback): void
/**
* @deprecated use new promise style API
*/
// @ts-ignore
removeObject(bucketName: string, objectName: string, callback: NoResultCallback): void
async removeObject(bucketName: string, objectName: string, removeOpts?: RemoveOptions): Promise<void>

async removeObject(bucketName: string, objectName: string, removeOpts: RemoveOptions = {}): Promise<void> {
async removeObject(bucketName: string, objectName: string, removeOpts?: RemoveOptions): Promise<void> {
if (!isValidBucketName(bucketName)) {
throw new errors.InvalidBucketNameError(`Invalid bucket name: ${bucketName}`)
}
if (!isValidObjectName(objectName)) {
throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`)
}

if (!isObject(removeOpts)) {
if (removeOpts && !isObject(removeOpts)) {
throw new errors.InvalidArgumentError('removeOpts should be of type "object"')
}

const method = 'DELETE'

const headers: RequestHeaders = {}
if (removeOpts.governanceBypass) {
if (removeOpts?.governanceBypass) {
headers['X-Amz-Bypass-Governance-Retention'] = true
}
if (removeOpts.forceDelete) {
if (removeOpts?.forceDelete) {
headers['x-minio-force-delete'] = true
}

const queryParams: Record<string, string> = {}
if (removeOpts.versionId) {
if (removeOpts?.versionId) {
queryParams.versionId = `${removeOpts.versionId}`
}
const query = qs.stringify(queryParams)
Expand Down Expand Up @@ -2429,4 +2420,37 @@ export class TypedClient {
const body = await readAsString(res)
return xmlParsers.parseObjectRetentionConfig(body)
}

async removeObjects(bucketName: string, objectsList: RemoveObjectsParam): Promise<RemoveObjectsResponse[]> {
if (!isValidBucketName(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
}
if (!Array.isArray(objectsList)) {
throw new errors.InvalidArgumentError('objectsList should be a list')
}

const runDeleteObjects = async (batch: RemoveObjectsParam): Promise<RemoveObjectsResponse[]> => {
const delObjects: RemoveObjectsRequestEntry[] = batch.map((value) => {
return isObject(value) ? { Key: value.name, VersionId: value.versionId } : { Key: value }
})

const remObjects = { Delete: { Quiet: true, Object: delObjects } }
const payload = Buffer.from(new xml2js.Builder({ headless: true }).buildObject(remObjects))
const headers: RequestHeaders = { 'Content-MD5': toMd5(payload) }

const res = await this.makeRequestAsync({ method: 'POST', bucketName, query: 'delete', headers }, payload)
const body = await readAsString(res)
return xmlParsers.removeObjectsParser(body)
}

const maxEntries = 1000 // max entries accepted in server for DeleteMultipleObjects API.
// Client side batching
const batches = []
for (let i = 0; i < objectsList.length; i += maxEntries) {
batches.push(objectsList.slice(i, i + maxEntries))
}

const batchResults = await Promise.all(batches.map(runDeleteObjects))
return batchResults.flat()
}
}
25 changes: 25 additions & 0 deletions src/internal/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,3 +391,28 @@ export type ObjectRetentionInfo = {
mode: RETENTION_MODES
retainUntilDate: string
}

export type RemoveObjectsEntry = {
name: string
versionId?: string
}
export type ObjectName = string

export type RemoveObjectsParam = ObjectName[] | RemoveObjectsEntry[]

export type RemoveObjectsRequestEntry = {
Key: string
VersionId?: string
}

export type RemoveObjectsResponse =
| null
| undefined
| {
Error?: {
Code?: string
Message?: string
Key?: string
VersionId?: string
}
}
9 changes: 9 additions & 0 deletions src/internal/xml-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,3 +557,12 @@ export function parseObjectRetentionConfig(xml: string) {
retainUntilDate: retentionConfig.RetainUntilDate,
}
}

export function removeObjectsParser(xml: string) {
const xmlObj = parseXml(xml)
if (xmlObj.DeleteResult && xmlObj.DeleteResult.Error) {
// return errors as array always. as the response is object in case of single object passed in removeObjects
return toArray(xmlObj.DeleteResult.Error)
}
return []
}
3 changes: 0 additions & 3 deletions src/minio.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,6 @@ export class Client extends TypedClient {
conditions: CopyConditions,
): Promise<BucketItemCopy>

removeObjects(bucketName: string, objectsList: string[], callback: NoResultCallback): void
removeObjects(bucketName: string, objectsList: string[]): Promise<void>

removeIncompleteUpload(bucketName: string, objectName: string, callback: NoResultCallback): void
removeIncompleteUpload(bucketName: string, objectName: string): Promise<void>
composeObject(
Expand Down
Loading
Loading