diff --git a/src/utilities/manifestUpdateUtils.test.ts b/src/utilities/manifestUpdateUtils.test.ts index 26fb0eaf3..fa23c9791 100644 --- a/src/utilities/manifestUpdateUtils.test.ts +++ b/src/utilities/manifestUpdateUtils.test.ts @@ -25,4 +25,83 @@ describe('manifestUpdateUtils', () => { manifestUpdateUtils.moveFilesToTmpDir(originalFilePaths) expect(newFilePaths).toEqual(expected) }) + + describe('updateImagesInK8sObject', () => { + it('updates image in Deployment containers', () => { + const obj: any = { + kind: 'Deployment', + spec: {template: {spec: {containers: [{image: 'nginx:old'}]}}} + } + manifestUpdateUtils['updateImagesInK8sObject']( + obj, + 'nginx', + 'nginx:new' + ) + expect(obj.spec.template.spec.containers[0].image).toBe('nginx:new') + }) + + it('updates image in CronJob containers', () => { + const obj: any = { + kind: 'CronJob', + spec: { + jobTemplate: { + spec: { + template: { + spec: {containers: [{image: 'busybox:old'}]} + } + } + } + } + } + manifestUpdateUtils['updateImagesInK8sObject']( + obj, + 'busybox', + 'busybox:new' + ) + expect( + obj.spec.jobTemplate.spec.template.spec.containers[0].image + ).toBe('busybox:new') + }) + + it('does not update image if name does not match', () => { + const obj: any = { + kind: 'Deployment', + spec: {template: {spec: {containers: [{image: 'nginx:old'}]}}} + } + manifestUpdateUtils['updateImagesInK8sObject']( + obj, + 'redis', + 'redis:new' + ) + expect(obj.spec.template.spec.containers[0].image).toBe('nginx:old') + }) + + it('updates image in initContainers for Deployment', () => { + const obj: any = { + kind: 'Deployment', + spec: {template: {spec: {initContainers: [{image: 'init:old'}]}}} + } + manifestUpdateUtils['updateImagesInK8sObject'](obj, 'init', 'init:new') + expect(obj.spec.template.spec.initContainers[0].image).toBe('init:new') + }) + + it('updates image in initContainers for CronJob', () => { + const obj: any = { + kind: 'CronJob', + spec: { + jobTemplate: { + spec: { + template: { + spec: {initContainers: [{image: 'init:old'}]} + } + } + } + } + } + manifestUpdateUtils['updateImagesInK8sObject'](obj, 'init', 'init:new') + expect( + obj.spec.jobTemplate.spec.template.spec.initContainers[0].image + ).toBe('init:new') + }) + }) }) diff --git a/src/utilities/manifestUpdateUtils.ts b/src/utilities/manifestUpdateUtils.ts index 9f7718458..42b1e148c 100644 --- a/src/utilities/manifestUpdateUtils.ts +++ b/src/utilities/manifestUpdateUtils.ts @@ -79,70 +79,71 @@ function updateContainerImagesInManifestFiles( filePaths: string[], containers: string[] ): string[] { - if (filePaths?.length <= 0) return filePaths + if (!filePaths?.length) return filePaths - // update container images filePaths.forEach((filePath: string) => { - let contents = fs.readFileSync(filePath).toString() - containers.forEach((container: string) => { - let [imageName] = container.split(':') - if (imageName.indexOf('@') > 0) { - imageName = imageName.split('@')[0] - } + const fileContents = fs.readFileSync(filePath, 'utf8') + const inputObjects = yaml.loadAll(fileContents) as K8sObject[] - if (contents.indexOf(imageName) > 0) - contents = substituteImageNameInSpecFile( - contents, - imageName, - container - ) - }) + const updatedObjects = inputObjects.map((obj) => { + if (!isWorkloadEntity(obj.kind)) return obj - // write updated files - fs.writeFileSync(path.join(filePath), contents) - }) + containers.forEach((container: string) => { + let [imageName] = container.split(':') + if (imageName.includes('@')) { + imageName = imageName.split('@')[0] + } + updateImagesInK8sObject(obj, imageName, container) + }) + return obj + }) + const newYaml = updatedObjects.map((o) => yaml.dump(o)).join('---\n') + fs.writeFileSync(path.join(filePath), newYaml) + }) return filePaths } -/* - Example: - - Input of - currentString: `image: "example/example-image"` - imageName: `example/example-image` - imageNameWithNewTag: `example/example-image:identifiertag` - - would return - `image: "example/example-image:identifiertag"` -*/ -export function substituteImageNameInSpecFile( - spec: string, +export function updateImagesInK8sObject( + obj: any, imageName: string, - imageNameWithNewTag: string + newImage: string ) { - if (spec.indexOf(imageName) < 0) return spec - - return spec.split('\n').reduce((acc, line) => { - const imageKeyword = line.match(/^ *-? *image:/) - if (imageKeyword) { - let [currentImageName] = line - .substring(imageKeyword[0].length) // consume the line from keyword onwards - .trim() - .replace(/[',"]/g, '') // replace allowed quotes with nothing - .split(':') - - if (currentImageName?.indexOf(' ') > 0) { - currentImageName = currentImageName.split(' ')[0] // remove comments - } - - if (currentImageName === imageName) { - return acc + `${imageKeyword[0]} ${imageNameWithNewTag}\n` - } + const isCronJob = obj?.kind?.toLowerCase() === KubernetesWorkload.CRON_JOB + + const containerPaths = [ + // Regular workload + obj?.spec?.template?.spec, + // CronJob workload + isCronJob ? obj?.spec?.jobTemplate?.spec?.template?.spec : null + ].filter(Boolean) // Remove any undefined/null entries + + for (const path of containerPaths) { + if (path?.containers) { + updateImageInContainerArray(path.containers, imageName, newImage) } + if (path?.initContainers) { + updateImageInContainerArray(path.initContainers, imageName, newImage) + } + } +} - return acc + line + '\n' - }, '') +function updateImageInContainerArray( + containers: any[], + imageName: string, + newImage: string +) { + if (!Array.isArray(containers)) return + containers.forEach((container) => { + if ( + container.image && + (container.image === imageName || + container.image.startsWith(imageName + ':') || + container.image.startsWith(imageName + '@')) + ) { + container.image = newImage + } + }) } export function getReplicaCount(inputObject: any): any {