From cf891e33a85201159694ef47b54d5e5e4c737349 Mon Sep 17 00:00:00 2001 From: Elanthenral Elangovan Date: Tue, 6 Jan 2026 13:46:39 -0800 Subject: [PATCH] ACNA-4093: Cleanup tmp S3 creds after app deploy --- src/deploy-web.js | 41 +++++++++++++++++++++++++---------- src/undeploy-web.js | 27 ++++++++++++++++++----- test/src/deploy-web.test.js | 25 +++++++++++++++++++++ test/src/undeploy-web.test.js | 8 +++++++ 4 files changed, 83 insertions(+), 18 deletions(-) diff --git a/src/deploy-web.js b/src/deploy-web.js index f140d55..cd771ba 100644 --- a/src/deploy-web.js +++ b/src/deploy-web.js @@ -30,22 +30,39 @@ const deployWeb = async (config, log) => { throw new Error(`missing files in ${dist}, maybe you forgot to build your UI ?`) } - const creds = await getS3Credentials(config) + // Use the same default cache file as TvmClient + const credsCacheFile = (config.s3 && config.s3.credsCacheFile) || '.aws.tmp.creds.json' - const remoteStorage = new RemoteStorage(creds) - const exists = await remoteStorage.folderExists(config.s3.folder + '/') + try { + const creds = await getS3Credentials(config) - if (exists) { - if (log) { - log('warning: an existing deployment will be overwritten') + const remoteStorage = new RemoteStorage(creds) + const exists = await remoteStorage.folderExists(config.s3.folder + '/') + + if (exists) { + if (log) { + log('warning: an existing deployment will be overwritten') + } + await remoteStorage.emptyFolder(config.s3.folder + '/') } - await remoteStorage.emptyFolder(config.s3.folder + '/') - } - const _log = log ? (f) => log(`deploying ${path.relative(dist, f)}`) : null - await remoteStorage.uploadDir(dist, config.s3.folder, config, _log) + const _log = log ? (f) => log(`deploying ${path.relative(dist, f)}`) : null + await remoteStorage.uploadDir(dist, config.s3.folder, config, _log) - const url = `https://${config.ow.namespace}.${config.app.hostname}/index.html` - return url + const url = `https://${config.ow.namespace}.${config.app.hostname}/index.html` + return url + } finally { + // Cleanup TVM credentials cache file + if (credsCacheFile && fs.existsSync(credsCacheFile)) { + try { + fs.removeSync(credsCacheFile) + } catch (err) { + // Ignore cleanup errors - don't fail deployment if cleanup fails + if (log) { + log(`warning: failed to cleanup credentials cache: ${err.message}`) + } + } + } + } } module.exports = deployWeb diff --git a/src/undeploy-web.js b/src/undeploy-web.js index 5e5361b..b87a0da 100644 --- a/src/undeploy-web.js +++ b/src/undeploy-web.js @@ -12,21 +12,36 @@ governing permissions and limitations under the License. const RemoteStorage = require('../lib/remote-storage') const getS3Credentials = require('../lib/getS3Creds') +const fs = require('fs-extra') const undeployWeb = async (config) => { if (!config || !config.app || !config.app.hasFrontend) { throw new Error('cannot undeploy web, app has no frontend or config is invalid') } - const creds = await getS3Credentials(config) + // Use the same default cache file as TvmClient + const credsCacheFile = (config.s3 && config.s3.credsCacheFile) || '.aws.tmp.creds.json' - const remoteStorage = new RemoteStorage(creds) + try { + const creds = await getS3Credentials(config) - if (!(await remoteStorage.folderExists(config.s3.folder + '/'))) { - throw new Error(`cannot undeploy static files, there is no deployment for ${config.s3.folder}`) - } + const remoteStorage = new RemoteStorage(creds) + + if (!(await remoteStorage.folderExists(config.s3.folder + '/'))) { + throw new Error(`cannot undeploy static files, there is no deployment for ${config.s3.folder}`) + } - await remoteStorage.emptyFolder(config.s3.folder + '/') + await remoteStorage.emptyFolder(config.s3.folder + '/') + } finally { + // Cleanup TVM credentials cache file + if (credsCacheFile && fs.existsSync(credsCacheFile)) { + try { + fs.removeSync(credsCacheFile) + } catch (err) { + // Ignore cleanup errors - don't fail undeployment if cleanup fails + } + } + } } module.exports = undeployWeb diff --git a/test/src/deploy-web.test.js b/test/src/deploy-web.test.js index a909adc..a7f4df5 100644 --- a/test/src/deploy-web.test.js +++ b/test/src/deploy-web.test.js @@ -265,4 +265,29 @@ describe('deploy-web', () => { expect(folderExists).toHaveBeenLastCalledWith('nsfolder/') expect(emptyFolder).toHaveBeenLastCalledWith('nsfolder/') }) + + test('cleans up credentials cache file', async () => { + fs.removeSync.mockImplementation(() => {}) + await deployWeb({ ow: { namespace: 'ns', auth: 'password' }, s3: { credsCacheFile: 'test.json', folder: 'f' }, app: { hasFrontend: true, hostname: 'h' }, web: { distProd: 'dist' } }) + expect(fs.removeSync).toHaveBeenCalledWith('test.json') + }) + + test('logs cleanup errors', async () => { + const log = jest.fn() + fs.removeSync.mockImplementation(() => { throw new Error('fail') }) + await deployWeb({ ow: { namespace: 'ns', auth: 'password' }, s3: { credsCacheFile: 'test.json', folder: 'f' }, app: { hasFrontend: true, hostname: 'h' }, web: { distProd: 'dist' } }, log) + expect(log).toHaveBeenCalledWith('warning: failed to cleanup credentials cache: fail') + }) + + test('ignores cleanup errors without logger', async () => { + fs.removeSync.mockImplementation(() => { throw new Error('fail') }) + await expect(deployWeb({ ow: { namespace: 'ns', auth: 'password' }, s3: { credsCacheFile: 'test.json', folder: 'f' }, app: { hasFrontend: true, hostname: 'h' }, web: { distProd: 'dist' } })).resolves.toBeDefined() + }) + + test('skips cleanup when file missing', async () => { + fs.existsSync.mockImplementation(p => p !== 'test.json') + fs.removeSync.mockClear() + await deployWeb({ ow: { namespace: 'ns', auth: 'password' }, s3: { credsCacheFile: 'test.json', folder: 'f' }, app: { hasFrontend: true, hostname: 'h' }, web: { distProd: 'dist' } }) + expect(fs.removeSync).not.toHaveBeenCalled() + }) }) diff --git a/test/src/undeploy-web.test.js b/test/src/undeploy-web.test.js index a0847d7..8bdfbc0 100644 --- a/test/src/undeploy-web.test.js +++ b/test/src/undeploy-web.test.js @@ -92,4 +92,12 @@ describe('undeploy-web', () => { expect(mockRemoteStorageInstance.folderExists).toHaveBeenCalledWith('somefolder/') expect(mockRemoteStorageInstance.emptyFolder).not.toHaveBeenCalled() }) + + test('cleans up credentials cache file', async () => { + const fs = require('fs-extra') + fs.existsSync = jest.fn().mockReturnValue(true) + fs.removeSync = jest.fn().mockImplementation(() => { throw new Error('fail') }) + mockRemoteStorageInstance.folderExists.mockResolvedValue(true) + await expect(undeployWeb({ ow: { namespace: 'ns', auth: 'password' }, s3: { credsCacheFile: 'test.json', folder: 'f' }, app: { hasFrontend: true } })).resolves.toBeUndefined() + }) })