Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aa7336686 | ||
|
|
04b61b9744 | ||
|
|
d8d2e74810 | ||
|
|
5f5708a2b8 |
Generated
+550
-643
File diff suppressed because it is too large
Load Diff
@@ -13,8 +13,8 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.11.1",
|
"@actions/core": "^2.0.2",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^2.0.0",
|
||||||
"hooklib": "file:../hooklib",
|
"hooklib": "file:../hooklib",
|
||||||
"shlex": "^3.0.0",
|
"shlex": "^3.0.0",
|
||||||
"uuid": "^11.1.0"
|
"uuid": "^11.1.0"
|
||||||
|
|||||||
Generated
+234
-426
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,6 @@
|
|||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.11.1"
|
"@actions/core": "^2.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+450
-457
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,9 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.11.1",
|
"@actions/core": "^2.0.2",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^2.0.0",
|
||||||
"@actions/io": "^1.1.3",
|
"@actions/io": "^2.0.0",
|
||||||
"@kubernetes/client-node": "^1.3.0",
|
"@kubernetes/client-node": "^1.3.0",
|
||||||
"hooklib": "file:../hooklib",
|
"hooklib": "file:../hooklib",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
|||||||
@@ -58,14 +58,30 @@ export async function prepareJob(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let services: k8s.V1Container[] = []
|
let services: k8s.V1Container[] = []
|
||||||
|
let serviceNames: string[] = []
|
||||||
if (args.services?.length) {
|
if (args.services?.length) {
|
||||||
|
const occurrences = new Map<string, number>()
|
||||||
|
for (const s of args.services) {
|
||||||
|
const base = generateContainerName(s.image)
|
||||||
|
occurrences.set(base, (occurrences.get(base) || 0) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const indices = new Map<string, number>()
|
||||||
services = args.services.map(service => {
|
services = args.services.map(service => {
|
||||||
return createContainerSpec(
|
const base = generateContainerName(service.image)
|
||||||
service,
|
const total = occurrences.get(base) || 0
|
||||||
generateContainerName(service.image),
|
const idx = indices.get(base) || 0
|
||||||
false,
|
|
||||||
extension
|
let name: string
|
||||||
)
|
if (total > 1) {
|
||||||
|
name = `${base}-${idx}`
|
||||||
|
} else {
|
||||||
|
name = base
|
||||||
|
}
|
||||||
|
|
||||||
|
indices.set(base, idx + 1)
|
||||||
|
serviceNames.push(name)
|
||||||
|
return createContainerSpec(service, name, false, extension)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,14 +169,15 @@ export async function prepareJob(
|
|||||||
throw new Error(`failed to determine if the pod is alpine: ${message}`)
|
throw new Error(`failed to determine if the pod is alpine: ${message}`)
|
||||||
}
|
}
|
||||||
core.debug(`Setting isAlpine to ${isAlpine}`)
|
core.debug(`Setting isAlpine to ${isAlpine}`)
|
||||||
generateResponseFile(responseFile, args, createdPod, isAlpine)
|
generateResponseFile(responseFile, args, createdPod, isAlpine, serviceNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateResponseFile(
|
function generateResponseFile(
|
||||||
responseFile: string,
|
responseFile: string,
|
||||||
args: PrepareJobArgs,
|
args: PrepareJobArgs,
|
||||||
appPod: k8s.V1Pod,
|
appPod: k8s.V1Pod,
|
||||||
isAlpine: boolean
|
isAlpine: boolean,
|
||||||
|
serviceNames?: string[]
|
||||||
): void {
|
): void {
|
||||||
if (!appPod.metadata?.name) {
|
if (!appPod.metadata?.name) {
|
||||||
throw new Error('app pod must have metadata.name specified')
|
throw new Error('app pod must have metadata.name specified')
|
||||||
@@ -193,7 +210,9 @@ function generateResponseFile(
|
|||||||
|
|
||||||
if (args.services?.length) {
|
if (args.services?.length) {
|
||||||
const serviceContainerNames =
|
const serviceContainerNames =
|
||||||
args.services?.map(s => generateContainerName(s.image)) || []
|
serviceNames && serviceNames.length
|
||||||
|
? serviceNames
|
||||||
|
: args.services?.map(s => generateContainerName(s.image)) || []
|
||||||
|
|
||||||
response.context['services'] = appPod?.spec?.containers
|
response.context['services'] = appPod?.spec?.containers
|
||||||
?.filter(c => serviceContainerNames.includes(c.name))
|
?.filter(c => serviceContainerNames.includes(c.name))
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export async function runScriptStep(
|
|||||||
'SRC=/__w/_temp_pre',
|
'SRC=/__w/_temp_pre',
|
||||||
'DST=/__w/_temp',
|
'DST=/__w/_temp',
|
||||||
// Overwrite _runner_file_commands
|
// Overwrite _runner_file_commands
|
||||||
|
'cp -a "$SRC/_runner_file_commands/." "$DST/_runner_file_commands"',
|
||||||
`find "$SRC" -type f ! -path "*/_runner_file_commands/*" -exec sh -c '
|
`find "$SRC" -type f ! -path "*/_runner_file_commands/*" -exec sh -c '
|
||||||
rel="\${1#$2/}"
|
rel="\${1#$2/}"
|
||||||
target="$3/$rel"
|
target="$3/$rel"
|
||||||
|
|||||||
@@ -243,4 +243,37 @@ describe('Prepare job', () => {
|
|||||||
'ghcr.io/actions/actions-runner:latest'
|
'ghcr.io/actions/actions-runner:latest'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should create unique service container names when images collide', async () => {
|
||||||
|
// Use fixed, non-colliding high ports. (Kubernetes hostPort must be unique per node.)
|
||||||
|
prepareJobData.args.container.portMappings = ['31080:8080']
|
||||||
|
|
||||||
|
// make two services with the same image
|
||||||
|
const svc = JSON.parse(JSON.stringify(prepareJobData.args.services[0]))
|
||||||
|
const svc2 = JSON.parse(JSON.stringify(svc))
|
||||||
|
// Ensure unique host ports so the pod spec is valid even with two services.
|
||||||
|
// (Kubernetes hostPort must be unique per node.)
|
||||||
|
svc.portMappings = ['31081:80', '31082:8080']
|
||||||
|
svc2.portMappings = ['31083:80', '31084:8080']
|
||||||
|
prepareJobData.args.services = [svc, svc2]
|
||||||
|
// ensure registries are null as TestHelper expects
|
||||||
|
prepareJobData.args.services.forEach((s: any) => (s.registry = null))
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
|
||||||
|
).resolves.not.toThrow()
|
||||||
|
|
||||||
|
const content = JSON.parse(
|
||||||
|
fs.readFileSync(prepareJobOutputFilePath).toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(content.context.services).toBeTruthy()
|
||||||
|
expect(content.context.services.length).toBe(2)
|
||||||
|
|
||||||
|
const got = await getPodByName(content.state.jobPod)
|
||||||
|
const names = (got.spec?.containers || []).map(c => c.name)
|
||||||
|
|
||||||
|
// when images collide, names should be suffixed with -0, -1
|
||||||
|
expect(names).toEqual(expect.arrayContaining(['redis-0', 'redis-1']))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user