Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions src/utilities/manifestUpdateUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks good, can we add a test for updateContainerImagesInManifestFiles to validate that the yaml parsing works as expected

})
105 changes: 53 additions & 52 deletions src/utilities/manifestUpdateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading