Skip to content

Commit

Permalink
feat: support for the latest putBucketLifecycle api features (#757)
Browse files Browse the repository at this point in the history
  • Loading branch information
weiyie committed Mar 25, 2020
1 parent e1cb438 commit 17c2984
Show file tree
Hide file tree
Showing 12 changed files with 699 additions and 186 deletions.
21 changes: 18 additions & 3 deletions README.md
Expand Up @@ -791,9 +791,24 @@ parameters:
- [id] {String} rule id, if not set, OSS will auto create it with random string.
- prefix {String} store prefix
- status {String} rule status, allow values: `Enabled` or `Disabled`
- [days] {Number|String} expire after the `days`
- [date] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z`
`date` and `days` only set one.
- [expiration] {Object} specifies the expiration attribute of the lifecycle rules for the object.
- [days] {Number|String} expire after the `days`
- [createdBeforeDate] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z`
`createdBeforeDate` and `days` must have one.
- [abortMultipartUpload] {Object} Specifies the expiration attribute of the multipart upload tasks that are not complete.
- [days] {Number|String} expire after the `days`
- [createdBeforeDate] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z`
`createdBeforeDate` and `days` must have one.
- [transition] {Object} Specifies the time when an object is converted to the IA or archive storage class during a valid life cycle.
- storageClass {String} Specifies the storage class that objects that conform to the rule are converted into. allow values: `IA` or `Archive`
- [days] {Number|String} expire after the `days`
- [createdBeforeDate] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z`
`createdBeforeDate` and `days` must have one.
`expiration``abortMultipartUpload``transition` must have one.
- [tag] {Object} Specifies the object tag applicable to a rule. Multiple tags are supported.
- key {String} Indicates the tag key.
- value {String} Indicates the tag value.
`tag` cannot be used with `abortMultipartUpload`
- [options] {Object} optional parameters
- [timeout] {Number} the operation timeout

Expand Down
70 changes: 0 additions & 70 deletions lib/browser/bucket.js
Expand Up @@ -159,76 +159,6 @@ proto.deleteBucketLogging = async function deleteBucketLogging(name, options) {
};
};

// lifecycle

proto.putBucketLifecycle = async function putBucketLifecycle(name, rules, options) {
// rules: [rule, ...]
// rule: [id], prefix, status, expiration, [days or date]
// status: 'Enabled' or 'Disabled'
const params = this._bucketRequestParams('PUT', name, 'lifecycle', options);
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<LifecycleConfiguration>\n';
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
const expiration = rule.days ?
`<Days>${rule.days}</Days>`
:
`<Date>${rule.date}</Date>`;
const id = rule.id ? `<ID>${rule.id}</ID>\n` : '';
xml += ` <Rule>\n${id
} <Prefix>${rule.prefix}</Prefix>\n` +
` <Status>${rule.status}</Status>\n` +
` <Expiration>${expiration}</Expiration>\n` +
' </Rule>\n';
}
xml += '</LifecycleConfiguration>';
params.content = xml;
params.mime = 'xml';
params.successStatuses = [200];
const result = await this.request(params);
return {
res: result.res
};
};

proto.getBucketLifecycle = async function getBucketLifecycle(name, options) {
const params = this._bucketRequestParams('GET', name, 'lifecycle', options);
params.successStatuses = [200];
params.xmlResponse = true;
const result = await this.request(params);
let rules = result.data.Rule || null;
if (rules) {
if (!isArray(rules)) {
rules = [rules];
}
rules = rules.map((rule) => {
const item = {
id: rule.ID,
prefix: rule.Prefix,
status: rule.Status
};
if (rule.Expiration.Days) {
item.days = rule.Expiration.Days;
} else {
item.date = rule.Expiration.Date;
}
return item;
});
}
return {
rules,
res: result.res
};
};

proto.deleteBucketLifecycle = async function deleteBucketLifecycle(name, options) {
const params = this._bucketRequestParams('DELETE', name, 'lifecycle', options);
params.successStatuses = [204];
const result = await this.request(params);
return {
res: result.res
};
};

proto.putBucketCORS = async function putBucketCORS(name, rules, options) {
rules = rules || [];
assert(rules.length, 'rules is required');
Expand Down
5 changes: 4 additions & 1 deletion lib/browser/client.js
Expand Up @@ -105,7 +105,10 @@ merge(proto, require('../common/bucket/getBucketWebsite'));
merge(proto, require('../common/bucket/putBucketWebsite'));
merge(proto, require('../common/bucket/deleteBucketWebsite'));

// merge(proto, require('./bucket'));
// lifecycle
merge(proto, require('../common/bucket/getBucketLifecycle'));
merge(proto, require('../common/bucket/putBucketLifecycle'));
merge(proto, require('../common/bucket/deleteBucketLifecycle'));


// multipart upload
Expand Down
73 changes: 0 additions & 73 deletions lib/bucket.js
Expand Up @@ -186,79 +186,6 @@ proto.deleteBucketLogging = async function deleteBucketLogging(name, options) {
};
};

// lifecycle

proto.putBucketLifecycle = async function putBucketLifecycle(name, rules, options) {
this._checkBucketName(name);
// rules: [rule, ...]
// rule: [id], prefix, status, expiration, [days or date]
// status: 'Enabled' or 'Disabled'
const params = this._bucketRequestParams('PUT', name, 'lifecycle', options);
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<LifecycleConfiguration>\n';
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
const expiration = rule.days ?
`<Days>${rule.days}</Days>`
:
`<Date>${rule.date}</Date>`;
const id = rule.id ? `<ID>${rule.id}</ID>\n` : '';
xml += ` <Rule>\n${id
} <Prefix>${rule.prefix}</Prefix>\n` +
` <Status>${rule.status}</Status>\n` +
` <Expiration>${expiration}</Expiration>\n` +
' </Rule>\n';
}
xml += '</LifecycleConfiguration>';
params.content = xml;
params.mime = 'xml';
params.successStatuses = [200];
const result = await this.request(params);
return {
res: result.res
};
};

proto.getBucketLifecycle = async function getBucketLifecycle(name, options) {
this._checkBucketName(name);
const params = this._bucketRequestParams('GET', name, 'lifecycle', options);
params.successStatuses = [200];
params.xmlResponse = true;
const result = await this.request(params);
let rules = result.data.Rule || null;
if (rules) {
if (!isArray(rules)) {
rules = [rules];
}
rules = rules.map((rule) => {
const item = {
id: rule.ID,
prefix: rule.Prefix,
status: rule.Status
};
if (rule.Expiration.Days) {
item.days = rule.Expiration.Days;
} else {
item.date = rule.Expiration.Date;
}
return item;
});
}
return {
rules,
res: result.res
};
};

proto.deleteBucketLifecycle = async function deleteBucketLifecycle(name, options) {
this._checkBucketName(name);
const params = this._bucketRequestParams('DELETE', name, 'lifecycle', options);
params.successStatuses = [204];
const result = await this.request(params);
return {
res: result.res
};
};

proto.putBucketCORS = async function putBucketCORS(name, rules, options) {
this._checkBucketName(name);
rules = rules || [];
Expand Down
11 changes: 11 additions & 0 deletions lib/common/bucket/deleteBucketLifecycle.js
@@ -0,0 +1,11 @@
const proto = exports;

proto.deleteBucketLifecycle = async function deleteBucketLifecycle(name, options) {
this._checkBucketName(name);
const params = this._bucketRequestParams('DELETE', name, 'lifecycle', options);
params.successStatuses = [204];
const result = await this.request(params);
return {
res: result.res
};
};
33 changes: 33 additions & 0 deletions lib/common/bucket/getBucketLifecycle.js
@@ -0,0 +1,33 @@
const isArray = require('../utils/isArray');
const formatObjKey = require('../utils/formatObjKey');

const proto = exports;

proto.getBucketLifecycle = async function getBucketLifecycle(name, options) {
this._checkBucketName(name);
const params = this._bucketRequestParams('GET', name, 'lifecycle', options);
params.successStatuses = [200];
params.xmlResponse = true;
const result = await this.request(params);
let rules = result.data.Rule || null;
if (rules) {
if (!isArray(rules)) {
rules = [rules];
}
rules = rules.map((_) => {
if (_.ID) {
_.id = _.ID;
delete _.ID;
}
if (_.Tag && !isArray(_.Tag)) {
_.Tag = [_.Tag];
}
return formatObjKey(_, 'firstLowerCase');
});
}
return {
rules,
res: result.res
};
};

3 changes: 3 additions & 0 deletions lib/common/bucket/index.js
Expand Up @@ -15,3 +15,6 @@ merge(proto, require('./_checkBucketName'));
merge(proto, require('./getBucketWebsite'));
merge(proto, require('./putBucketWebsite'));
merge(proto, require('./deleteBucketWebsite'));
merge(proto, require('./getBucketLifecycle'));
merge(proto, require('./putBucketLifecycle'));
merge(proto, require('./deleteBucketLifecycle'));
122 changes: 122 additions & 0 deletions lib/common/bucket/putBucketLifecycle.js
@@ -0,0 +1,122 @@
/* eslint-disable no-use-before-define */

const isArray = require('../utils/isArray');
const deepCopy = require('../utils/deepCopy');
const isObject = require('../utils/isObject');
const obj2xml = require('../utils/obj2xml');
const checkObjectTag = require('../utils/checkObjectTag');
const getStrBytesCount = require('../utils/getStrBytesCount');

const proto = exports;


proto.putBucketLifecycle = async function putBucketLifecycle(name, rules, options) {
this._checkBucketName(name);

if (!isArray(rules)) {
throw new Error('rules must be Array');
}

const params = this._bucketRequestParams('PUT', name, 'lifecycle', options);
const Rule = [];
const paramXMLObj = {
LifecycleConfiguration: {
Rule
}
};

rules.forEach((_) => {
defaultDaysAndDate2Expiration(_); // todo delete, 兼容旧版本
checkRule(_);
if (_.id) {
_.ID = _.id;
delete _.id;
}
Rule.push(_);
});

const paramXML = obj2xml(paramXMLObj, {
headers: true,
firstUpperCase: true
});

params.content = paramXML;
params.mime = 'xml';
params.successStatuses = [200];
const result = await this.request(params);
return {
res: result.res
};
};

// todo delete, 兼容旧版本
function defaultDaysAndDate2Expiration(obj) {
if (obj.days) {
obj.expiration = {
days: obj.days
};
}
if (obj.date) {
obj.expiration = {
createdBeforeDate: obj.date
};
}
}

function checkDaysAndDate(obj, key) {
const { days, createdBeforeDate } = obj;
if (!days && !createdBeforeDate) {
throw new Error(`${key} must includes days or createdBeforeDate`);
} else if (days && !/^[1-9][0-9]*$/.test(days)) {
throw new Error('days must be a positive integer');
} else if (createdBeforeDate && !/\d{4}-\d{2}-\d{2}T00:00:00.000Z/.test(createdBeforeDate)) {
throw new Error('createdBeforeDate must be date and conform to iso8601 format');
}
}

function handleCheckTag(tag) {
if (!isArray(tag) && !isObject(tag)) {
throw new Error('tag must be Object or Array');
}
tag = isObject(tag) ? [tag] : tag;
const tagObj = {};
const tagClone = deepCopy(tag);
tagClone.forEach((v) => {
tagObj[v.key] = v.value;
});

checkObjectTag(tagObj);
}

function checkRule(rule) {
if (rule.id && getStrBytesCount(rule.id) > 255) throw new Error('ID is composed of 255 bytes at most');

if (rule.prefix === '' || rule.prefix === undefined) throw new Error('Rule must includes prefix');

if (!['Enabled', 'Disabled'].includes(rule.status)) throw new Error('Status must be Enabled or Disabled');

if (rule.transition) {
if (!['IA', 'Archive'].includes(rule.transition.storageClass)) throw new Error('StorageClass must be IA or Archive');
checkDaysAndDate(rule.transition, 'Transition');
}

if (rule.expiration) {
checkDaysAndDate(rule.expiration, 'Expiration');
}

if (rule.abortMultipartUpload) {
checkDaysAndDate(rule.abortMultipartUpload, 'AbortMultipartUpload');
}

if (!rule.expiration && !rule.abortMultipartUpload && !rule.transition) {
throw new Error('Rule must includes expiration or abortMultipartUpload or transition');
}

if (rule.tag) {
if (rule.abortMultipartUpload) {
throw new Error('Tag cannot be used with abortMultipartUpload');
}
handleCheckTag(rule.tag);
}
}

0 comments on commit 17c2984

Please sign in to comment.