Skip to content

Instantly share code, notes, and snippets.

@krasimir
Last active May 25, 2024 00:30
Show Gist options
  • Save krasimir/8dd6f608fc829e5e5aa9ab0136452706 to your computer and use it in GitHub Desktop.
Save krasimir/8dd6f608fc829e5e5aa9ab0136452706 to your computer and use it in GitHub Desktop.
Deleting artifacts in GCP's Cloud Storage (ignores latest and leaves at least two versions)
const spawn = require("child_process").spawn;
const KEEP_AT_LEAST = 2;
const CONTAINER_REGISTRIES = [
"gcr.io/<your project name>",
"eu.gcr.io/<your project name>/gcf/europe-west3"
];
async function go(registry) {
console.log(`> ${registry}`);
const images = await command(`gcloud`, [
"container",
"images",
"list",
`--repository=${registry}`,
"--format=json",
]);
for (let i = 0; i < images.length; i++) {
console.log(` ${images[i].name}`);
const image = images[i].name;
let tags = await command(`gcloud`, [
"container",
"images",
"list-tags",
image,
"--format=json",
]);
const totalImages = tags.length;
// do not touch `latest`
tags = tags.filter(({ tags }) => !tags.find((tag) => tag === "latest"));
// sorting by date
tags.sort((a, b) => {
const d1 = new Date(a.timestamp.datetime);
const d2 = new Date(b.timestamp.datetime);
return d2.getTime() - d1.getTime();
});
// keeping at least X number of images
tags = tags.filter((_, i) => i >= KEEP_AT_LEAST);
console.log(` For removal: ${tags.length}/${totalImages}`);
for (let j = 0; j < tags.length; j++) {
console.log(
` Deleting: ${formatImageTimestamp(tags[j])} | ${tags[j].digest}`
);
await command("gcloud", [
"container",
"images",
"delete",
`${image}@${tags[j].digest}`,
"--format=json",
"--quiet",
"--force-delete-tags",
]);
}
}
}
function command(cmd, args) {
return new Promise((done, reject) => {
const ps = spawn(cmd, args);
let result = "";
ps.stdout.on("data", (data) => {
result += data;
});
ps.stderr.on("data", (data) => {
result += data;
});
ps.on("close", (code) => {
if (code !== 0) {
console.log(`process exited with code ${code}`);
}
try {
done(JSON.parse(result));
} catch (err) {
done(result);
}
});
});
}
function formatImageTimestamp(image) {
const { year, month, day, hour, minute } = image.timestamp;
return `${year}-${month}-${day} ${hour}:${minute}`;
}
(async function () {
for (let i = 0; i < CONTAINER_REGISTRIES.length; i++) {
await go(CONTAINER_REGISTRIES[i]);
}
})();
@Jmaiers
Copy link

Jmaiers commented May 26, 2022

I was thinking of running this script against my artifacts. One question I have though, why keep any beyond latest? When I look at the Container Registry in GCP I only see 2 entries for each container, the latest and another. Is it safe to delete the other by setting KEEP_AT_LEAST to 0? With the way the script is written with the filters in place, it seems like if I set it to 0 the filter will keep the latest and get rid of the rest. Am I incorrect?

@krasimir
Copy link
Author

@Jmaiers yep, you are right. I guess I wanted to have an insurance in case of latest is messed up.

@Jmaiers
Copy link

Jmaiers commented May 26, 2022

@Jmaiers yep, you are right. I guess I wanted to have an insurance in case of latest is messed up.

Excellent! Thank you for the quick response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment