From 8e6890b4d6bba8fbfa6669cf441c7b74e4ffe877 Mon Sep 17 00:00:00 2001 From: Girish Ramakrishnan Date: Sat, 14 Dec 2024 20:47:35 +0100 Subject: [PATCH] docker: rework image pruning with our new retagging approach, the Digest ID remains because this is only set by docker if truly fetched from the registry. this means that redis container always gets removed... --- src/docker.js | 3 ++- src/mailserver.js | 2 ++ src/platform.js | 38 +------------------------------------- src/services.js | 12 ++++++++++++ src/sftp.js | 2 ++ 5 files changed, 19 insertions(+), 38 deletions(-) diff --git a/src/docker.js b/src/docker.js index d122ec37a..b26742742 100644 --- a/src/docker.js +++ b/src/docker.js @@ -586,7 +586,8 @@ async function deleteImage(imageRef) { // registry v1 used to pull down all *tags*. this meant that deleting image by tag was not enough (since that // just removes the tag). we used to remove the image by id. this is not required anymore because aliases are // not created anymore after https://github.com/docker/docker/pull/10571 - const [error] = await safe(gConnection.getImage(imageRef).remove(removeOptions)); + debug(`deleteImage: removing ${imageRef}`); + const [error] = await safe(gConnection.getImage(imageRef.replace(/@sha256:.*/,'')).remove(removeOptions)); // can't have the manifest id. won't remove anythin if (error && error.statusCode === 400) return; // invalid image format. this can happen if user installed with a bad --docker-image if (error && error.statusCode === 404) return; // not found if (error && error.statusCode === 409) return; // another container using the image diff --git a/src/mailserver.js b/src/mailserver.js index 61c13d473..8e8769f0f 100644 --- a/src/mailserver.js +++ b/src/mailserver.js @@ -213,6 +213,8 @@ async function start(existingInfra) { debug('startMail: starting'); await restart(); + + if (existingInfra.version !== 'none' && existingInfra.images.mail !== infra.images.mail) await docker.deleteImage(existingInfra.images.mail); } async function restartIfActivated() { diff --git a/src/platform.js b/src/platform.js index a031a9b65..2efb3d6ff 100644 --- a/src/platform.js +++ b/src/platform.js @@ -22,7 +22,6 @@ const apps = require('./apps.js'), dashboard = require('./dashboard.js'), database = require('./database.js'), debug = require('debug')('box:platform'), - docker = require('./docker.js'), dockerProxy = require('./dockerproxy.js'), fs = require('fs'), infra = require('./infra_version.js'), @@ -46,37 +45,6 @@ function getStatus() { return { message: gStatusMessage }; } -async function pruneInfraImages() { - debug('pruneInfraImages: checking existing images'); - - // cannot blindly remove all unused images since redis image may not be used - const imageRefs = Object.keys(infra.images).map(addon => infra.images[addon]); - const [error, output] = await safe(shell.bash('docker images --digests --format "{{.ID}} {{.Repository}} {{.Tag}} {{.Digest}}"', { encoding: 'utf8' })); - if (error) { - debug(`Failed to list images ${error.message}`); - throw error; - } - const lines = output.trim().split('\n'); - - for (const imageRef of imageRefs) { - const parsedRef = docker.parseImageRef(imageRef); - - for (const line of lines) { - if (!line) continue; - const [, repo, tag, digest] = line.split(' '); // [ ID, Repo, Tag, Digest ] - const normalizedRepo = repo.replace('registry.ipv6.docker.com/', '').replace('registry-1.docker.io/', '').replace('registry.docker.com/', ''); - if (!parsedRef.fullRepositoryName.endsWith(normalizedRepo)) continue; // some other repo - if (imageRef === `${repo}:${tag}@${digest}`) continue; // the image we want to keep - - const imageIdToPrune = tag === '' ? `${repo}@${digest}` : `${repo}:${tag}`; // untagged, use digest - console.log(`pruneInfraImages: removing unused image of ${imageRef}: ${imageIdToPrune}`); - - const [error] = await safe(shell.spawn('docker', [ 'rmi', imageIdToPrune ], {})); - if (error) console.log(`Error removing image ${imageIdToPrune}: ${error.mesage}`); - } - } -} - async function pruneVolumes() { debug('pruneVolumes: remove all unused local volumes'); @@ -138,11 +106,7 @@ async function onInfraReady(infraChanged) { debug(`onInfraReady: platform is ready. infra changed: ${infraChanged}`); gStatusMessage = 'Ready'; - if (infraChanged) { - await safe(pruneInfraImages(), { debug }); // ignore error - await safe(pruneVolumes(), { debug }); // ignore error - } - + if (infraChanged) await safe(pruneVolumes(), { debug }); // ignore error await apps.schedulePendingTasks(AuditSource.PLATFORM); await appTaskManager.start(); } diff --git a/src/services.js b/src/services.js index 55cb5cfac..25ea813b9 100644 --- a/src/services.js +++ b/src/services.js @@ -1035,6 +1035,8 @@ async function startTurn(existingInfra) { debug('startTurn: starting turn container'); await shell.bash(runCmd, {}); + + if (existingInfra.version !== 'none' && existingInfra.images.turn !== image) await docker.deleteImage(existingInfra.images.turn); } async function teardownTurn(app, options) { @@ -1248,6 +1250,8 @@ async function startMysql(existingInfra) { await waitForContainer('mysql', 'CLOUDRON_MYSQL_TOKEN'); if (upgrading) await importDatabase('mysql'); } + + if (existingInfra.version !== 'none' && existingInfra.images.mysql !== image) await docker.deleteImage(existingInfra.images.mysql); } async function setupMySql(app, options) { @@ -1466,6 +1470,8 @@ async function startPostgresql(existingInfra) { await waitForContainer('postgresql', 'CLOUDRON_POSTGRESQL_TOKEN'); if (upgrading) await importDatabase('postgresql'); } + + if (existingInfra.version !== 'none' && existingInfra.images.postgresql !== image) await docker.deleteImage(existingInfra.images.postgresql); } async function setupPostgreSql(app, options) { @@ -1615,6 +1621,8 @@ async function startMongodb(existingInfra) { await waitForContainer('mongodb', 'CLOUDRON_MONGODB_TOKEN'); if (upgrading) await importDatabase('mongodb'); } + + if (existingInfra.version !== 'none' && existingInfra.images.mongodb !== image) await docker.deleteImage(existingInfra.images.mongodb); } async function setupMongoDb(app, options) { @@ -1782,6 +1790,8 @@ async function startGraphite(existingInfra) { debug('startGraphite: starting graphite container'); await shell.bash(runCmd, {}); + if (existingInfra.version !== 'none' && existingInfra.images.graphite !== image) await docker.deleteImage(existingInfra.images.graphite); + // restart collectd to get the disk stats after graphite starts. currently, there is no way to do graphite health check setTimeout(async () => await safe(shell.promises.sudo([ RESTART_SERVICE_CMD, 'collectd' ], {})), 60000); } @@ -1872,6 +1882,8 @@ async function startRedis(existingInfra) { } if (upgrading) await importDatabase('redis'); + + if (existingInfra.version !== 'none' && existingInfra.images.redis !== image) await docker.deleteImage(existingInfra.images.redis); } // Ensures that app's addon redis container is running. Can be called when named container already exists/running diff --git a/src/sftp.js b/src/sftp.js index 8988924a6..2985d420a 100644 --- a/src/sftp.js +++ b/src/sftp.js @@ -125,6 +125,8 @@ async function start(existingInfra) { debug('startSftp: starting sftp container'); await shell.bash(runCmd, {}); + + if (existingInfra.version !== 'none' && existingInfra.images.sftp !== image) await docker.deleteImage(existingInfra.images.sftp); } async function status() {