Cloudflare Pages: alte Deployments nach 7 Tagen löschen

Den Source-Code von dem Cloudflare Worker hänge ich unten an.

Unter Cloudflare einen neuen Worker erstellem. Via „Einstellungen“ > „Variablen und geheime Schlüssel“ folgende Sachen angeben:

TypNameWert
KlartextCLOUDFLARE_ACCOUNT_IDDie Account-ID, die auch bei Wrangler zum Deployment genutzt wird.
GeheimnisCLOUDFLARE_API_SECRETDen API-Key; kann auch von Wrangler übernommen werden
KlartextCLOUDFLARE_PAGENAMEDer Name der Seite; zum Beispiel tinokuptzblog

Sieht im UI wie folgt aus:

Unter Auslöseereignisse einen neuen Cron-Trigger hinzufügen. Intervall = Tag des Monats, Tag = Täglich, zur UTC-Zeit = 03:00 (beispielsweise).

Der Worker wird anschließend jeden Tag ausgeführt, liest jeden alle Deployments die älter als 7 Tage sind aus und löscht diese.

Er kann via Worker-Overfläche direkt gestartet werden um zu schauen, ob er sauber läuft. Via console.log loggt er, was er tut, und warum er das macht. Das neueste Deployment skippt er automatisch, denn das ist ja i.d.R. das, welches auch live genutzt wird – und somit sowieso gelöscht werden kann.

Der Code von dem Worker:

const expirationDays = 7;

/**
 * Liest die Base-URL aus
 * @param {object} env
 * @returns {string}
 */
const getBaseUrl = (env) => `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/pages/projects/${env.CLOUDFLARE_PAGENAME}/deployments`;

/**
 * Holt sich die Deployments, und gibt diese als Array zurück.
 * Das letzte Deployment kann nicht gelöscht werden - dieses wird also weggeschnitten
 * @returns {Promise<object[]>}
 */
const fetchDeployments = async (env) => {
  console.log(`fetching deployments`)
  const fetchParams = {
    headers: {
      "Content-Type": "application/json;charset=UTF-8",
      "Authorization": `Bearer ${env.CLOUDFLARE_API_TOKEN}`,
    },
  };
  const response = await fetch(getBaseUrl(env), fetchParams);
  var ret = await response.json();
  ret = ret.result;
  // @todo: hier beachten, dass die aktuelle Live-Deployment ggfs nicht die aktuellste ist
  // Stand jetzt geht der Worker davon aus, dass der letzte deployte auch immer live ist
  ret.shift();
  // Nun noch mal direkt zur Verarbeitung anpassen
  const now = Date.now();
  for (var i = 0; i < ret.length; i++) {
    ret[i].age = {
      created: new Date(ret[i].created_on),
    };
    ret[i].age.hours = Math.round((now - ret[i].age.created) / (1000 * 60 * 60));
    ret[i].age.days = Math.round(ret[i].age.hours / 24);
  }
  return ret;
};

/**
 * Löscht ein Deployment
 * @param {object} env
 * @param {string} id
 * @returns {Promise}
 */
const deleteDeployment = async (env, id) => {
  console.log(`fetching deployments`)
  const fetchParams = {
    method: "DELETE",
    headers: {
      "Content-Type": "application/json;charset=UTF-8",
      "Authorization": `Bearer ${env.CLOUDFLARE_API_TOKEN}`,
    },
  };
  return fetch(`${getBaseUrl(env)}/{id}`, fetchParams);
}

export default {
  async fetch(request, env, ctx) {
    const deployments = await fetchDeployments(env);

    var promiseQueue = [];
    var retJson = { skipped: [], deleted: [] };
    for (const deployment of deployments) {
      if (deployment.age.days < expirationDays) {
        console.log(`skipping deployment ${deployment.short_id}: only ${deployment.age.days} days old`)
        retJson.skipped.push(deployment.short_id)
        continue;
      }
      console.log(`deleting deployment ${deployment.short_id} (${deployment.age.days} days old)`)
      promiseQueue.push(deleteDeployment(env, deployment.id))
      retJson.deleted.push(deployment.short_id)
    }

    console.log(`waiting for all ${promiseQueue.length} api calls to finish`)
    await Promise.all(promiseQueue);

    return new Response(JSON.stringify(retJson))
  },

  async scheduled(_, env) {
    const deployments = await fetchDeployments(env);

    var promiseQueue = [];
    for (const deployment of deployments) {
      if (deployment.age.days < expirationDays) {
        console.log(`skipping deployment ${deployment.short_id}: only ${deployment.age.days} days old`)
        continue;
      }
      console.log(`deleting deployment ${deployment.short_id} (${deployment.age.days} days old)`)
      promiseQueue.push(deleteDeployment(env, deployment.id));
    }

    console.log(`waiting for all ${promiseQueue.length} api calls to finish`)
    await Promise.all(promiseQueue);
  }
}