Skip to content

Instantly share code, notes, and snippets.

@erik-am
Created May 15, 2020 13:24
Show Gist options
  • Save erik-am/01c0d676c087fef125034e45eefd7428 to your computer and use it in GitHub Desktop.
Save erik-am/01c0d676c087fef125034e45eefd7428 to your computer and use it in GitHub Desktop.
'use strict';
class PrDetector {
constructor(serverless) {
this.serverless = serverless;
this.provider = this.serverless.getProvider('aws');
this.hooks = {
'after:aws:package:finalize:mergeCustomProviderResources': () =>
this.removeDeletionPolicies(),
'before:remove:remove': async () => await this.emptyS3Buckets(),
};
this.log = message => serverless.cli.log(message, 'PR-Detector');
}
removeDeletionPolicies() {
if (this.isPr()) {
this.log('Detected a Pull Request');
let cfResources = this.serverless.service.provider
.compiledCloudFormationTemplate.Resources;
for (let key in cfResources) {
if (cfResources[key].DeletionPolicy) {
this.log(`Removing deletion policy from ${key}`);
delete cfResources[key].DeletionPolicy;
}
if (cfResources[key].UpdateReplacePolicy) {
this.log(`Removing update/replace policy from ${key}`);
delete cfResources[key].UpdateReplacePolicy;
}
}
}
}
isPr() {
const stage = this.serverless.variables.options.stage;
const isPr = stage.match(/^pr-([0-9]+)$/);
return isPr;
}
async emptyS3Buckets() {
if (this.isPr()) {
const resources = this.serverless.service.resources.Resources;
const buckets = Object.keys(resources)
.filter(x => resources[x].Type === 'AWS::S3::Bucket')
.map(x => resources[x].Properties.BucketName)
.filter(x => x !== null);
buckets.forEach(async bucket => {
this.log(`Emptying S3 bucket ${bucket}`);
await this.getAllKeys(bucket)
.then(params => this.executeRemove(params))
.then(() => {
this.log(`Success: ${bucket} is empty.`);
})
.catch(err => {
console.log(err);
this.log(`Failed: ${bucket} may not be empty.`);
});
});
}
}
// From https://github.com/sinofseven/serverless-s3-remover/blob/master/index.js (MIT)
getAllKeys(bucket) {
const get = (src = {}) => {
const data = src.data;
const keys = src.keys || [];
const param = {
Bucket: bucket,
};
if (data) {
param.ContinuationToken = data.NextContinuationToken;
}
return this.provider
.request('S3', 'listObjectsV2', param)
.then(result => {
return new Promise(resolve => {
resolve({
data: result,
keys: keys.concat(
result.Contents.map(item => {
return item.Key;
}),
),
});
});
});
};
const list = (src = {}) => {
return get(src).then(result => {
if (result.data.IsTruncated) {
return list(result);
} else {
const keys = result.keys;
const batched = [];
for (let i = 0; i < keys.length; i += 1000) {
const objects = keys.slice(i, i + 1000).map(item => {
return { Key: item };
});
batched.push({
Bucket: bucket,
Delete: {
Objects: objects,
},
});
}
return new Promise(resolve => {
resolve(batched);
});
}
});
};
return list();
}
executeRemove(params) {
return Promise.all(
params.map(param => {
return this.provider.request('S3', 'deleteObjects', param);
}),
);
}
}
module.exports = PrDetector;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment