-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathaction.js
114 lines (103 loc) · 3.03 KB
/
action.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
'use strict';
function promisify(object, fnc, param) {
return new Promise((resolve, reject) => {
fnc.call(object, param, (err, data) => {
if (err) {
reject(err);
}
else {
resolve(data);
}
});
});
}
// get a set of object keys from an s3 bucket
function listKeys(s3, prefix) {
return promisify(s3, s3.listObjects, {Prefix: prefix})
.catch((err) => {
throw Object({message: `s3 list objects failed ${err.message}`});
})
.then((data) => data.Contents.map((x) => x.Key))
;
}
// delete a set of object keys from an s3 bucket
function deleteKeys(s3, keySet) {
if (keySet.length === 0) {
return Promise.resolve();
}
const param = {
Delete: {
Objects: keySet.map((x) => ({Key: x})),
Quiet: true,
},
};
return promisify(s3, s3.deleteObjects, param)
.catch((err) => {
throw Object({message: `s3 delete objects failed ${err.message}`});
})
.then(() => undefined)
;
}
const S3 = require('aws-sdk/clients/s3');
const s3 = new S3({
region: process.env.AWS_REGION === 's3'
? 'us-east-1'
: process.env.AWS_REGION
,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
params: {
Bucket: process.env.AWS_BUCKET,
},
});
const prefix = process.env.PRUNE_PREFIX;
const postfix = process.env.PRUNE_POSTFIX;
const maxKeys = Number.parseInt(process.env.MAX_OBJECTS, 10);
function objectKeyToTimestamp(key) {
if (key.substr(0, prefix.length) !== prefix) {
return null;
}
if (key.substr(-1 * postfix.length) !== postfix) {
return null;
}
return (new Date(key.substr(prefix.length, key.length - prefix.length - postfix.length))).valueOf();
}
// main
Promise.resolve()
.then(() => listKeys(s3, prefix))
// reduce to keys to delete
.then((unfilteredKeyList) => {
// reduce to an ordered list of valid timestamp keys
const keyList = unfilteredKeyList.filter(objectKeyToTimestamp).sort();
// identify surplus keys
const young = objectKeyToTimestamp(keyList[keyList.length - 1]);
const old = objectKeyToTimestamp(keyList[0]);
const surplusKeyList = [];
const numSets = Math.ceil(maxKeys / 2);
for (let i = 1; i < numSets; i++) {
const oldBoundary = Math.log(i) / Math.log(numSets) * (young - old) + old;
const youngBoundary = Math.log(i + 1) / Math.log(numSets) * (young - old) + old;
const setList = keyList
.filter((key) => objectKeyToTimestamp(key) >= oldBoundary)
.filter((key) => objectKeyToTimestamp(key) <= youngBoundary)
;
for (let j = 1; j < setList.length - 1; j++) {
surplusKeyList.push(setList[j]);
}
}
const numDelete = keyList.length > maxKeys ? keyList.length - maxKeys : 0;
// from the surplus select the oldest that are excessive
console.log(`s3 objects prune - ${keyList.length} objects, ${maxKeys} max, ${numDelete} to be deleted`);
return surplusKeyList.slice(0, numDelete);
})
// delete keys
.then((deleteKeyList) => deleteKeys(s3, deleteKeyList))
.then(() => {
console.log('s3 objects prune - complete');
})
.catch((err) => {
console.error(`s3 objects prune - ${err.message}`);
})
;