Skip to content

Commit 17c2984

Browse files
authored
feat: support for the latest putBucketLifecycle api features (#757)
1 parent e1cb438 commit 17c2984

File tree

12 files changed

+699
-186
lines changed

12 files changed

+699
-186
lines changed

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -791,9 +791,24 @@ parameters:
791791
- [id] {String} rule id, if not set, OSS will auto create it with random string.
792792
- prefix {String} store prefix
793793
- status {String} rule status, allow values: `Enabled` or `Disabled`
794-
- [days] {Number|String} expire after the `days`
795-
- [date] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z`
796-
`date` and `days` only set one.
794+
- [expiration] {Object} specifies the expiration attribute of the lifecycle rules for the object.
795+
- [days] {Number|String} expire after the `days`
796+
- [createdBeforeDate] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z`
797+
`createdBeforeDate` and `days` must have one.
798+
- [abortMultipartUpload] {Object} Specifies the expiration attribute of the multipart upload tasks that are not complete.
799+
- [days] {Number|String} expire after the `days`
800+
- [createdBeforeDate] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z`
801+
`createdBeforeDate` and `days` must have one.
802+
- [transition] {Object} Specifies the time when an object is converted to the IA or archive storage class during a valid life cycle.
803+
- storageClass {String} Specifies the storage class that objects that conform to the rule are converted into. allow values: `IA` or `Archive`
804+
- [days] {Number|String} expire after the `days`
805+
- [createdBeforeDate] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z`
806+
`createdBeforeDate` and `days` must have one.
807+
`expiration``abortMultipartUpload``transition` must have one.
808+
- [tag] {Object} Specifies the object tag applicable to a rule. Multiple tags are supported.
809+
- key {String} Indicates the tag key.
810+
- value {String} Indicates the tag value.
811+
`tag` cannot be used with `abortMultipartUpload`
797812
- [options] {Object} optional parameters
798813
- [timeout] {Number} the operation timeout
799814

lib/browser/bucket.js

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -159,76 +159,6 @@ proto.deleteBucketLogging = async function deleteBucketLogging(name, options) {
159159
};
160160
};
161161

162-
// lifecycle
163-
164-
proto.putBucketLifecycle = async function putBucketLifecycle(name, rules, options) {
165-
// rules: [rule, ...]
166-
// rule: [id], prefix, status, expiration, [days or date]
167-
// status: 'Enabled' or 'Disabled'
168-
const params = this._bucketRequestParams('PUT', name, 'lifecycle', options);
169-
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<LifecycleConfiguration>\n';
170-
for (let i = 0; i < rules.length; i++) {
171-
const rule = rules[i];
172-
const expiration = rule.days ?
173-
`<Days>${rule.days}</Days>`
174-
:
175-
`<Date>${rule.date}</Date>`;
176-
const id = rule.id ? `<ID>${rule.id}</ID>\n` : '';
177-
xml += ` <Rule>\n${id
178-
} <Prefix>${rule.prefix}</Prefix>\n` +
179-
` <Status>${rule.status}</Status>\n` +
180-
` <Expiration>${expiration}</Expiration>\n` +
181-
' </Rule>\n';
182-
}
183-
xml += '</LifecycleConfiguration>';
184-
params.content = xml;
185-
params.mime = 'xml';
186-
params.successStatuses = [200];
187-
const result = await this.request(params);
188-
return {
189-
res: result.res
190-
};
191-
};
192-
193-
proto.getBucketLifecycle = async function getBucketLifecycle(name, options) {
194-
const params = this._bucketRequestParams('GET', name, 'lifecycle', options);
195-
params.successStatuses = [200];
196-
params.xmlResponse = true;
197-
const result = await this.request(params);
198-
let rules = result.data.Rule || null;
199-
if (rules) {
200-
if (!isArray(rules)) {
201-
rules = [rules];
202-
}
203-
rules = rules.map((rule) => {
204-
const item = {
205-
id: rule.ID,
206-
prefix: rule.Prefix,
207-
status: rule.Status
208-
};
209-
if (rule.Expiration.Days) {
210-
item.days = rule.Expiration.Days;
211-
} else {
212-
item.date = rule.Expiration.Date;
213-
}
214-
return item;
215-
});
216-
}
217-
return {
218-
rules,
219-
res: result.res
220-
};
221-
};
222-
223-
proto.deleteBucketLifecycle = async function deleteBucketLifecycle(name, options) {
224-
const params = this._bucketRequestParams('DELETE', name, 'lifecycle', options);
225-
params.successStatuses = [204];
226-
const result = await this.request(params);
227-
return {
228-
res: result.res
229-
};
230-
};
231-
232162
proto.putBucketCORS = async function putBucketCORS(name, rules, options) {
233163
rules = rules || [];
234164
assert(rules.length, 'rules is required');

lib/browser/client.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ merge(proto, require('../common/bucket/getBucketWebsite'));
105105
merge(proto, require('../common/bucket/putBucketWebsite'));
106106
merge(proto, require('../common/bucket/deleteBucketWebsite'));
107107

108-
// merge(proto, require('./bucket'));
108+
// lifecycle
109+
merge(proto, require('../common/bucket/getBucketLifecycle'));
110+
merge(proto, require('../common/bucket/putBucketLifecycle'));
111+
merge(proto, require('../common/bucket/deleteBucketLifecycle'));
109112

110113

111114
// multipart upload

lib/bucket.js

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -186,79 +186,6 @@ proto.deleteBucketLogging = async function deleteBucketLogging(name, options) {
186186
};
187187
};
188188

189-
// lifecycle
190-
191-
proto.putBucketLifecycle = async function putBucketLifecycle(name, rules, options) {
192-
this._checkBucketName(name);
193-
// rules: [rule, ...]
194-
// rule: [id], prefix, status, expiration, [days or date]
195-
// status: 'Enabled' or 'Disabled'
196-
const params = this._bucketRequestParams('PUT', name, 'lifecycle', options);
197-
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<LifecycleConfiguration>\n';
198-
for (let i = 0; i < rules.length; i++) {
199-
const rule = rules[i];
200-
const expiration = rule.days ?
201-
`<Days>${rule.days}</Days>`
202-
:
203-
`<Date>${rule.date}</Date>`;
204-
const id = rule.id ? `<ID>${rule.id}</ID>\n` : '';
205-
xml += ` <Rule>\n${id
206-
} <Prefix>${rule.prefix}</Prefix>\n` +
207-
` <Status>${rule.status}</Status>\n` +
208-
` <Expiration>${expiration}</Expiration>\n` +
209-
' </Rule>\n';
210-
}
211-
xml += '</LifecycleConfiguration>';
212-
params.content = xml;
213-
params.mime = 'xml';
214-
params.successStatuses = [200];
215-
const result = await this.request(params);
216-
return {
217-
res: result.res
218-
};
219-
};
220-
221-
proto.getBucketLifecycle = async function getBucketLifecycle(name, options) {
222-
this._checkBucketName(name);
223-
const params = this._bucketRequestParams('GET', name, 'lifecycle', options);
224-
params.successStatuses = [200];
225-
params.xmlResponse = true;
226-
const result = await this.request(params);
227-
let rules = result.data.Rule || null;
228-
if (rules) {
229-
if (!isArray(rules)) {
230-
rules = [rules];
231-
}
232-
rules = rules.map((rule) => {
233-
const item = {
234-
id: rule.ID,
235-
prefix: rule.Prefix,
236-
status: rule.Status
237-
};
238-
if (rule.Expiration.Days) {
239-
item.days = rule.Expiration.Days;
240-
} else {
241-
item.date = rule.Expiration.Date;
242-
}
243-
return item;
244-
});
245-
}
246-
return {
247-
rules,
248-
res: result.res
249-
};
250-
};
251-
252-
proto.deleteBucketLifecycle = async function deleteBucketLifecycle(name, options) {
253-
this._checkBucketName(name);
254-
const params = this._bucketRequestParams('DELETE', name, 'lifecycle', options);
255-
params.successStatuses = [204];
256-
const result = await this.request(params);
257-
return {
258-
res: result.res
259-
};
260-
};
261-
262189
proto.putBucketCORS = async function putBucketCORS(name, rules, options) {
263190
this._checkBucketName(name);
264191
rules = rules || [];
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const proto = exports;
2+
3+
proto.deleteBucketLifecycle = async function deleteBucketLifecycle(name, options) {
4+
this._checkBucketName(name);
5+
const params = this._bucketRequestParams('DELETE', name, 'lifecycle', options);
6+
params.successStatuses = [204];
7+
const result = await this.request(params);
8+
return {
9+
res: result.res
10+
};
11+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const isArray = require('../utils/isArray');
2+
const formatObjKey = require('../utils/formatObjKey');
3+
4+
const proto = exports;
5+
6+
proto.getBucketLifecycle = async function getBucketLifecycle(name, options) {
7+
this._checkBucketName(name);
8+
const params = this._bucketRequestParams('GET', name, 'lifecycle', options);
9+
params.successStatuses = [200];
10+
params.xmlResponse = true;
11+
const result = await this.request(params);
12+
let rules = result.data.Rule || null;
13+
if (rules) {
14+
if (!isArray(rules)) {
15+
rules = [rules];
16+
}
17+
rules = rules.map((_) => {
18+
if (_.ID) {
19+
_.id = _.ID;
20+
delete _.ID;
21+
}
22+
if (_.Tag && !isArray(_.Tag)) {
23+
_.Tag = [_.Tag];
24+
}
25+
return formatObjKey(_, 'firstLowerCase');
26+
});
27+
}
28+
return {
29+
rules,
30+
res: result.res
31+
};
32+
};
33+

lib/common/bucket/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ merge(proto, require('./_checkBucketName'));
1515
merge(proto, require('./getBucketWebsite'));
1616
merge(proto, require('./putBucketWebsite'));
1717
merge(proto, require('./deleteBucketWebsite'));
18+
merge(proto, require('./getBucketLifecycle'));
19+
merge(proto, require('./putBucketLifecycle'));
20+
merge(proto, require('./deleteBucketLifecycle'));
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/* eslint-disable no-use-before-define */
2+
3+
const isArray = require('../utils/isArray');
4+
const deepCopy = require('../utils/deepCopy');
5+
const isObject = require('../utils/isObject');
6+
const obj2xml = require('../utils/obj2xml');
7+
const checkObjectTag = require('../utils/checkObjectTag');
8+
const getStrBytesCount = require('../utils/getStrBytesCount');
9+
10+
const proto = exports;
11+
12+
13+
proto.putBucketLifecycle = async function putBucketLifecycle(name, rules, options) {
14+
this._checkBucketName(name);
15+
16+
if (!isArray(rules)) {
17+
throw new Error('rules must be Array');
18+
}
19+
20+
const params = this._bucketRequestParams('PUT', name, 'lifecycle', options);
21+
const Rule = [];
22+
const paramXMLObj = {
23+
LifecycleConfiguration: {
24+
Rule
25+
}
26+
};
27+
28+
rules.forEach((_) => {
29+
defaultDaysAndDate2Expiration(_); // todo delete, 兼容旧版本
30+
checkRule(_);
31+
if (_.id) {
32+
_.ID = _.id;
33+
delete _.id;
34+
}
35+
Rule.push(_);
36+
});
37+
38+
const paramXML = obj2xml(paramXMLObj, {
39+
headers: true,
40+
firstUpperCase: true
41+
});
42+
43+
params.content = paramXML;
44+
params.mime = 'xml';
45+
params.successStatuses = [200];
46+
const result = await this.request(params);
47+
return {
48+
res: result.res
49+
};
50+
};
51+
52+
// todo delete, 兼容旧版本
53+
function defaultDaysAndDate2Expiration(obj) {
54+
if (obj.days) {
55+
obj.expiration = {
56+
days: obj.days
57+
};
58+
}
59+
if (obj.date) {
60+
obj.expiration = {
61+
createdBeforeDate: obj.date
62+
};
63+
}
64+
}
65+
66+
function checkDaysAndDate(obj, key) {
67+
const { days, createdBeforeDate } = obj;
68+
if (!days && !createdBeforeDate) {
69+
throw new Error(`${key} must includes days or createdBeforeDate`);
70+
} else if (days && !/^[1-9][0-9]*$/.test(days)) {
71+
throw new Error('days must be a positive integer');
72+
} else if (createdBeforeDate && !/\d{4}-\d{2}-\d{2}T00:00:00.000Z/.test(createdBeforeDate)) {
73+
throw new Error('createdBeforeDate must be date and conform to iso8601 format');
74+
}
75+
}
76+
77+
function handleCheckTag(tag) {
78+
if (!isArray(tag) && !isObject(tag)) {
79+
throw new Error('tag must be Object or Array');
80+
}
81+
tag = isObject(tag) ? [tag] : tag;
82+
const tagObj = {};
83+
const tagClone = deepCopy(tag);
84+
tagClone.forEach((v) => {
85+
tagObj[v.key] = v.value;
86+
});
87+
88+
checkObjectTag(tagObj);
89+
}
90+
91+
function checkRule(rule) {
92+
if (rule.id && getStrBytesCount(rule.id) > 255) throw new Error('ID is composed of 255 bytes at most');
93+
94+
if (rule.prefix === '' || rule.prefix === undefined) throw new Error('Rule must includes prefix');
95+
96+
if (!['Enabled', 'Disabled'].includes(rule.status)) throw new Error('Status must be Enabled or Disabled');
97+
98+
if (rule.transition) {
99+
if (!['IA', 'Archive'].includes(rule.transition.storageClass)) throw new Error('StorageClass must be IA or Archive');
100+
checkDaysAndDate(rule.transition, 'Transition');
101+
}
102+
103+
if (rule.expiration) {
104+
checkDaysAndDate(rule.expiration, 'Expiration');
105+
}
106+
107+
if (rule.abortMultipartUpload) {
108+
checkDaysAndDate(rule.abortMultipartUpload, 'AbortMultipartUpload');
109+
}
110+
111+
if (!rule.expiration && !rule.abortMultipartUpload && !rule.transition) {
112+
throw new Error('Rule must includes expiration or abortMultipartUpload or transition');
113+
}
114+
115+
if (rule.tag) {
116+
if (rule.abortMultipartUpload) {
117+
throw new Error('Tag cannot be used with abortMultipartUpload');
118+
}
119+
handleCheckTag(rule.tag);
120+
}
121+
}
122+

0 commit comments

Comments
 (0)