Skip to content

Instantly share code, notes, and snippets.

@mrcoles
Created June 25, 2019 22:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrcoles/1a252ccffca03cebd9376ffe0612e944 to your computer and use it in GitHub Desktop.
Save mrcoles/1a252ccffca03cebd9376ffe0612e944 to your computer and use it in GitHub Desktop.
A script for deleting old amplify builds in s3 and keeping the 5 most recent ones.
const AWS = require('aws-sdk');
const PREFIX = 'amplify-builds/';
const DEFAULT_KEEP_N = 5;
const DEFAULT_DRY_RUN = false;
const s3 = new AWS.S3();
// ## Main
const main = async (
bucket,
keepN = DEFAULT_KEEP_N,
dryRun = DEFAULT_DRY_RUN
) => {
const prefix = PREFIX;
const result = await s3
.listObjects({ Bucket: bucket, Prefix: prefix })
.promise();
const files = result.Contents;
const groupedFiles = _groupByFirstToken(files, prefix);
_forEach(groupedFiles, (key, val) => {
const was = val.length;
val = _sort(val);
val = _excludeLastN(val, keepN);
groupedFiles[key] = val;
});
//
console.log('Builds to delete:');
_forEach(groupedFiles, (key, val) => {
console.log(`* ${key}: ${val.length} file${val.length === 1 ? '' : 's'}`);
});
if (dryRun) {
console.log('skipping for dry-run');
} else {
console.log('');
for ([key, val] of Object.entries(groupedFiles)) {
await s3DeleteObjects(bucket, val.map(file => file.Key));
console.log(
`deleted ${val.length} file${val.length === 1 ? '' : 's'} from ${key}`
);
}
}
};
// ## Helpers
const s3DeleteObjects = async (bucket, keys) => {
// TODO - handle > 1000 keys
if (!keys.length) {
return;
}
const params = {
Bucket: bucket,
Delete: {
Objects: keys.map(key => ({ Key: key })),
Quiet: true
}
};
return s3.deleteObjects(params).promise();
};
const _forEach = (obj, callback) =>
Object.entries(obj).forEach(([key, val]) => callback(key, val));
// ### Group By First Token
//
// Takes the Key field and removes prefix and takes the first
// value, e.g., "myfunction/users-abcdef-1235.zip" -> "users"
// @param files: S3File[]
// @param prefix: string
// @returns { [key: string]: val: S3File[] }
//
const _groupByFirstToken = (files, prefix = '') => {
const keyToFiles = {};
files.forEach(file => {
const key = file.Key.substring(prefix.length).split('-')[0];
if (keyToFiles[key] === undefined) {
keyToFiles[key] = [];
}
keyToFiles[key].push(file);
});
return keyToFiles;
};
// ### Sort Files
//
// Sorts by the LastModified field ascending (oldest first)
// @returns files: S3File[] - a new files array
const _sort = files => {
files = [...files];
files.sort((a, b) => a.LastModified - b.LastModified);
return files;
};
// ### Exclude Last N
//
// @param arr: any[]
// @param n: number
// @returns any[] - a new array excluding the last n values
// or an empty one if there are not enough
const _excludeLastN = (arr, n) => {
const len = arr.length;
const end = Math.max(0, len - n);
return arr.slice(0, end);
};
// ## CLI
if (require.main === module) {
const commandWrapper = prom =>
prom.catch(err => {
console.error(err);
process.exit(1);
});
const argv = require('yargs')
.usage('Usage: $0 <bucketName>')
.command(
'$0 <bucketName>',
'Clean up old amplify-builds',
yargs =>
yargs
.positional('bucketName', {
type: 'string',
describe: 'The key for your AWS S3 bucket',
required: true
})
.option('dryRun', {
alias: 'n',
type: 'boolean',
demandOption: false,
describe: 'perform a dry run',
default: DEFAULT_DRY_RUN
})
.option('keep', {
alias: 'k',
type: 'number',
demandOption: false,
default: DEFAULT_KEEP_N
}),
argv => commandWrapper(main(argv.bucketName, argv.keep, argv.dryRun))
)
.strict()
.help().argv;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment