Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f503f27d3 | ||
|
|
287a0458a1 | ||
|
|
b8af7ebe0e |
Generated
+386
-371
File diff suppressed because it is too large
Load Diff
@@ -13,21 +13,21 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^2.0.2",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/exec": "^2.0.0",
|
"@actions/exec": "^2.0.0",
|
||||||
"hooklib": "file:../hooklib",
|
"hooklib": "file:../hooklib",
|
||||||
"shlex": "^3.0.0",
|
"shlex": "^3.0.0",
|
||||||
"uuid": "^11.1.0"
|
"uuid": "^13.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.28.5",
|
||||||
"@babel/preset-env": "^7.25.4",
|
"@babel/preset-env": "^7.28.5",
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^24.0.14",
|
"@types/node": "^24.0.14",
|
||||||
"@typescript-eslint/parser": "^8.37.0",
|
"@typescript-eslint/parser": "^8.49.0",
|
||||||
"@vercel/ncc": "^0.38.3",
|
"@vercel/ncc": "^0.38.3",
|
||||||
"jest": "^30.0.4",
|
"jest": "^30.0.4",
|
||||||
"ts-jest": "^29.4.0",
|
"ts-jest": "^29.4.6",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
|
|||||||
Generated
+427
-235
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": "^2.0.2"
|
"@actions/core": "^1.11.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+457
-450
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,9 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^2.0.2",
|
"@actions/core": "^1.11.1",
|
||||||
"@actions/exec": "^2.0.0",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/io": "^2.0.0",
|
"@actions/io": "^1.1.3",
|
||||||
"@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,30 +58,14 @@ 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 => {
|
||||||
const base = generateContainerName(service.image)
|
return createContainerSpec(
|
||||||
const total = occurrences.get(base) || 0
|
service,
|
||||||
const idx = indices.get(base) || 0
|
generateContainerName(service.image),
|
||||||
|
false,
|
||||||
let name: string
|
extension
|
||||||
if (total > 1) {
|
)
|
||||||
name = `${base}-${idx}`
|
|
||||||
} else {
|
|
||||||
name = base
|
|
||||||
}
|
|
||||||
|
|
||||||
indices.set(base, idx + 1)
|
|
||||||
serviceNames.push(name)
|
|
||||||
return createContainerSpec(service, name, false, extension)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,15 +153,14 @@ 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, serviceNames)
|
generateResponseFile(responseFile, args, createdPod, isAlpine)
|
||||||
}
|
}
|
||||||
|
|
||||||
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')
|
||||||
@@ -210,9 +193,7 @@ function generateResponseFile(
|
|||||||
|
|
||||||
if (args.services?.length) {
|
if (args.services?.length) {
|
||||||
const serviceContainerNames =
|
const serviceContainerNames =
|
||||||
serviceNames && serviceNames.length
|
args.services?.map(s => generateContainerName(s.image)) || []
|
||||||
? 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,7 +48,6 @@ 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,37 +243,4 @@ 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