Skip to content

Instantly share code, notes, and snippets.

@furlanf
Last active April 1, 2021 01:45
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 furlanf/9467d152b6bac0b7212f401292358334 to your computer and use it in GitHub Desktop.
Save furlanf/9467d152b6bac0b7212f401292358334 to your computer and use it in GitHub Desktop.
Old script to clean unused lambda layers versions based on used lambda functions
const AWS = require("aws-sdk");
const retry = require("async-retry");
const lambda = new AWS.Lambda();
const bailIfErrorNotRetryable = (bail) => (error) => {
if (!error.retryable) {
bail(error);
} else {
throw error;
}
};
const getRetryConfig = (onRetry) => ({
retries: parseInt(process.env.RETRIES || "5"),
minTimeout: parseFloat(process.env.RETRY_MIN_TIMEOUT || "5000"),
maxTimeout: parseFloat(process.env.RETRY_MAX_TIMEOUT || "60000"),
factor: 2,
onRetry,
});
/**
* https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#listFunctions-property
* @returns array of ARN lambda layers used by your active functions
*/
const listFunctionsLayers = async () => {
console.info("listing all available functions...");
const loop = async (acc = [], marker) => {
const params = {
Marker: marker,
MaxItems: 50,
};
const res = await retry(
(bail) =>
lambda
.listFunctions(params)
.promise()
.catch(bailIfErrorNotRetryable(bail)),
getRetryConfig((err) => {
console.warn("retrying listFunctions after error...", err);
})
);
let layers = [];
res.Functions.map((f) => {
if (f.Layers) {
layers.push(f.Layers.map((l) => l.Arn));
}
});
const newAcc = acc.concat(...new Set(layers));
if (res.NextMarker) {
return loop(newAcc, res.NextMarker);
} else {
console.info(`found ${newAcc.length} functions`, {
count: newAcc.length,
});
return [...new Set(newAcc)];
}
};
return loop();
};
/**
* Lists AWS Lambda layers and shows information about the latest version of each.
* @returns array of registered layers
*/
const listLayers = async () => {
console.debug("listing layers...");
const loop = async (acc = [], marker) => {
const params = {
Marker: marker,
MaxItems: 20,
};
const res = await retry(
(bail) =>
lambda
.listLayers(params)
.promise()
.catch(bailIfErrorNotRetryable(bail)),
getRetryConfig((err) => {
console.warn(
"retrying listLayers after error...",
{ function: funcArn },
err
);
})
);
const layer = res.Layers.map((x) => x.LayerName);
const newAcc = acc.concat(layer);
if (res.NextMarker) {
return loop(newAcc, res.NextMarker);
} else {
return newAcc;
}
};
return loop();
};
/**
* https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#listLayerVersions-property
* Lists the versions of an AWS Lambda layer filtered by the layer name
*/
const listLayerVersion = async (layerName) => {
console.debug("listing versions...", { function: layerName });
const loop = async (acc = [], marker) => {
const params = {
LayerName: layerName,
Marker: marker,
MaxItems: 50,
};
const res = await retry(
(bail) =>
lambda
.listLayerVersions(params)
.promise()
.catch(bailIfErrorNotRetryable(bail)),
getRetryConfig((err) => {
console.warn(
"retrying listLayerVersions after error...",
{ function: layerName },
err
);
})
);
const versions = res.LayerVersions.map((x) => x.Version);
const newAcc = acc.concat(...versions);
if (res.NextMarker) {
return loop(newAcc, res.NextMarker);
} else {
return newAcc;
}
};
return loop();
};
/**
* https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#deleteLayerVersion-property
* Deletes a version of an AWS Lambda layer.
*/
const deleteLayerVersion = async (LayerName, VersionNumber) => {
console.info("deleting...", { layer: LayerName, VersionNumber });
var params = {
LayerName: LayerName,
VersionNumber: VersionNumber,
};
await retry(
(bail) =>
lambda
.deleteLayerVersion(params)
.promise()
.catch(bailIfErrorNotRetryable(bail)),
getRetryConfig((err) => {
console.warn(
"retrying deleteFunction after error...",
{ function: funcArn, version },
err
);
})
);
};
const handler = async () => {
await clean();
};
const getUsedVersions = (layers, name) => {
const versionsToNotDelete = [];
for (const layerArn of layers) {
const splittedArn = layerArn.split(":");
const layerName = splittedArn[6];
const layerVersion = splittedArn[7];
if (layerName === name) {
console.log("found >> ", { layerVersion });
versionsToNotDelete.push(parseInt(layerVersion));
}
}
console.log("version to not delete >> ", { versionsToNotDelete });
return versionsToNotDelete;
};
const clean = async () => {
const layersArn = await listFunctionsLayers(); // Get all used ARN (Amazon Resource Name) Layers
const layerNames = await listLayers(); // List all lambda layers
console.log(`${layersArn.length} used layers...`);
for (const name of layerNames) {
console.log("layerName >> ", { name });
const layerVersions = await listLayerVersion(name); // Get all versions filtered by layers name
console.log(`layers to clean...`, {
versions: layerVersions,
count: layerVersions.length,
});
const usedVersions = getUsedVersions(layersArn, name); // verify if version is being used
const versionsToDelete = layerVersions.filter(
(version) => !usedVersions.find((not) => not === version)
);
console.log("version to delete >> ", {
layerName: name,
versionsToDelete,
count: versionsToDelete.length,
});
deleteLayer(name, versionsToDelete);
}
};
const deleteLayer = async (layerName, versions) => {
for (const version of versions) {
await deleteLayerVersion(layerName, version);
}
};
handler();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment