Skip to content

Commit 68897b6

Browse files
committed
wip
1 parent eaf3d54 commit 68897b6

8 files changed

Lines changed: 137 additions & 178 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
},
99
"scripts": {
1010
"test": "npm run test --prefix packages/docker && npm run test --prefix packages/k8s",
11+
"test:docker": "npm run test --prefix packages/docker",
12+
"test:k8s": "npm run test --prefix packages/k8s",
1113
"bootstrap": "npm ci --prefix packages/hooklib && npm ci --prefix packages/k8s && npm ci --prefix packages/docker",
1214
"format": "prettier --write '**/*.ts'",
1315
"format-check": "prettier --check '**/*.ts'",

packages/k8s/src/k8s/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@ export async function isPodContainerAlpine(
525525
[
526526
'sh',
527527
'-c',
528-
`'[ $(cat /etc/*release* | grep -i -e "^ID=*alpine*" -c) != 0 ] || exit 1'`
528+
'[ $(cat /etc/*release* | grep -i -e "^ID=*alpine*" -c) != 0 ] || exit 1'
529529
],
530530
podName,
531531
containerName

packages/k8s/src/k8s/utils.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,14 @@ export function containerVolumes(
9191
'Volume mounts outside of the work folder are not supported'
9292
)
9393
}
94-
sourceVolumePath = userVolume.sourceVolumePath.slice(
94+
const i = workspacePath.lastIndexOf('_work/')
95+
const workspaceRelativePath = workspacePath.slice(i + '_work/'.length)
96+
const sourceRelativePath = userVolume.sourceVolumePath.slice(
9597
workspacePath.length + 1
9698
)
99+
sourceVolumePath = sourceRelativePath
100+
? path.posix.join(workspaceRelativePath, sourceRelativePath)
101+
: workspaceRelativePath
97102
} else {
98103
sourceVolumePath = userVolume.sourceVolumePath
99104
}
@@ -397,5 +402,8 @@ function mergeLists<T>(base?: T[], from?: T[]): T[] {
397402
}
398403

399404
export function fixArgs(args: string[]): string[] {
405+
if (args.length >= 2 && args[0] === 'sh' && args[1] === '-c') {
406+
return args
407+
}
400408
return shlex.split(args.join(' '))
401409
}

packages/k8s/tests/prepare-job-test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ describe('Prepare job', () => {
5050
prepareJobData.args.container.userMountVolumes = [
5151
{
5252
sourceVolumePath: userVolumeMount,
53-
targetVolumePath: '/__w/myvolume',
53+
targetVolumePath: '/myvolume',
5454
readOnly: false
5555
}
5656
]
@@ -63,7 +63,7 @@ describe('Prepare job', () => {
6363
)
6464

6565
await execPodStep(
66-
['sh', '-c', '[ "$(cat /__w/myvolume/file.txt)" = "hello" ] || exit 5'],
66+
['sh', '-c', '[ "$(cat /myvolume/file.txt)" = "hello" ] || exit 5'],
6767
content!.state!.jobPod,
6868
JOB_CONTAINER_NAME
6969
).then(output => {

packages/k8s/tests/run-container-step-test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('Run container step', () => {
4242
{
4343
name: JOB_CONTAINER_EXTENSION_NAME,
4444
command: ['sh'],
45-
args: ['-c', 'sleep 10000']
45+
args: ['-c', 'echo test']
4646
},
4747
{
4848
name: 'side-container',
@@ -64,6 +64,10 @@ describe('Run container step', () => {
6464
delete process.env[ENV_HOOK_TEMPLATE_PATH]
6565
})
6666

67+
afterEach(() => {
68+
delete process.env[ENV_HOOK_TEMPLATE_PATH]
69+
})
70+
6771
it('should shold have env variables available', async () => {
6872
runContainerStepData.args.entryPoint = 'bash'
6973
runContainerStepData.args.entryPointArgs = [
Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
import {
2-
isRWXTestEnabled,
3-
getRWXStorageClass,
4-
RWX_SKIP_MESSAGE
5-
} from './test-setup'
1+
import * as k8s from '@kubernetes/client-node'
62

7-
describe('RWX Test Contract Demo', () => {
8-
const describeOrSkip = isRWXTestEnabled() ? describe : describe.skip
3+
const kc = new k8s.KubeConfig()
4+
kc.loadFromDefault()
5+
const k8sApi = kc.makeApiClient(k8s.StorageV1Api)
96

10-
describeOrSkip('RWX volume tests', () => {
11-
it('should use RWX storage class when enabled', () => {
12-
const storageClass = getRWXStorageClass()
13-
expect(storageClass).toBeDefined()
14-
expect(typeof storageClass).toBe('string')
7+
describe('RWX Test Contract Demo', () => {
8+
describe('RWX volume tests', () => {
9+
it('should have at least one available storage class', async () => {
10+
const list = await k8sApi.listStorageClass()
11+
expect(list.items.length).toBeGreaterThan(0)
1512
})
1613

17-
it('should verify both env vars are required', () => {
18-
expect(process.env.ACTIONS_RUNNER_K8S_TEST_ENABLE_RWX).toBe('true')
19-
expect(
20-
process.env.ACTIONS_RUNNER_K8S_TEST_RWX_STORAGE_CLASS
21-
).toBeDefined()
14+
it('should have at least one default storage class in cluster', async () => {
15+
const list = await k8sApi.listStorageClass()
16+
const hasDefault = list.items.some(sc => {
17+
const annotations = sc.metadata?.annotations || {}
18+
return (
19+
annotations['storageclass.kubernetes.io/is-default-class'] ===
20+
'true' ||
21+
annotations['storageclass.beta.kubernetes.io/is-default-class'] ===
22+
'true'
23+
)
24+
})
25+
expect(hasDefault).toBe(true)
2226
})
2327
})
24-
25-
if (!isRWXTestEnabled()) {
26-
it(RWX_SKIP_MESSAGE, () => {})
27-
}
2828
})
Lines changed: 97 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,126 @@
11
import * as k8s from '@kubernetes/client-node'
22
import * as fs from 'fs'
3+
import * as path from 'path'
34
import { cleanupJob, prepareJob, runScriptStep } from '../src/hooks'
4-
import {
5-
TestHelper,
6-
isRWXTestEnabled,
7-
getRWXStorageClass,
8-
RWX_SKIP_MESSAGE
9-
} from './test-setup'
5+
import { TestHelper } from './test-setup'
106
import { RunScriptStepArgs } from 'hooklib'
117

128
jest.useRealTimers()
139

1410
const kc = new k8s.KubeConfig()
1511
kc.loadFromDefault()
1612
const k8sApi = kc.makeApiClient(k8s.CoreV1Api)
13+
const k8sStorageApi = kc.makeApiClient(k8s.StorageV1Api)
1714

1815
describe('RWX Volume Tests', () => {
19-
const describeOrSkip = isRWXTestEnabled() ? describe : describe.skip
20-
21-
describeOrSkip('RWX volume integration', () => {
22-
let testHelper: TestHelper
23-
let rwxPvcName: string
24-
let prepareJobData: any
25-
let prepareJobOutputFilePath: string
26-
27-
beforeEach(async () => {
28-
testHelper = new TestHelper()
29-
await testHelper.initialize()
30-
31-
const podName = process.env.ACTIONS_RUNNER_POD_NAME
32-
rwxPvcName = `${podName}-work-rwx`
33-
34-
const volumeClaim: k8s.V1PersistentVolumeClaim = {
35-
metadata: {
36-
name: rwxPvcName
37-
},
38-
spec: {
39-
accessModes: ['ReadWriteMany'],
40-
volumeMode: 'Filesystem',
41-
storageClassName: getRWXStorageClass(),
42-
resources: {
43-
requests: {
44-
storage: '1Gi'
45-
}
46-
}
47-
}
16+
let testHelper: TestHelper
17+
let rwxPvcName: string
18+
let rwxPvName: string
19+
let rwxStorageClassName: string
20+
let prepareJobData: any
21+
let prepareJobOutputFilePath: string
22+
23+
beforeEach(async () => {
24+
testHelper = new TestHelper()
25+
await testHelper.initialize()
26+
27+
const podName = process.env.ACTIONS_RUNNER_POD_NAME as string
28+
const runnerWorkspace = process.env.RUNNER_WORKSPACE as string
29+
const runnerWorkRoot = path.resolve(runnerWorkspace, '..')
30+
31+
rwxPvcName = `${podName}-work-rwx`
32+
rwxPvName = `${podName}-work-rwx-pv`
33+
rwxStorageClassName = `${podName}-work-rwx-storage`
34+
35+
const sc: k8s.V1StorageClass = {
36+
metadata: { name: rwxStorageClassName },
37+
provisioner: 'kubernetes.io/no-provisioner',
38+
volumeBindingMode: 'Immediate'
39+
}
40+
await k8sStorageApi.createStorageClass({ body: sc })
41+
42+
const pv: k8s.V1PersistentVolume = {
43+
metadata: { name: rwxPvName },
44+
spec: {
45+
storageClassName: rwxStorageClassName,
46+
capacity: { storage: '2Gi' },
47+
volumeMode: 'Filesystem',
48+
accessModes: ['ReadWriteMany'],
49+
hostPath: { path: runnerWorkRoot }
4850
}
51+
}
52+
await k8sApi.createPersistentVolume({ body: pv })
53+
54+
const volumeClaim: k8s.V1PersistentVolumeClaim = {
55+
metadata: { name: rwxPvcName },
56+
spec: {
57+
accessModes: ['ReadWriteMany'],
58+
volumeMode: 'Filesystem',
59+
storageClassName: rwxStorageClassName,
60+
volumeName: rwxPvName,
61+
resources: { requests: { storage: '1Gi' } }
62+
}
63+
}
4964

50-
await k8sApi.createNamespacedPersistentVolumeClaim({
51-
namespace: 'default',
52-
body: volumeClaim
53-
})
54-
55-
process.env.ACTIONS_RUNNER_CLAIM_NAME = rwxPvcName
56-
57-
prepareJobData = testHelper.getPrepareJobDefinition()
58-
prepareJobOutputFilePath = testHelper.createFile(
59-
'prepare-job-output.json'
60-
)
65+
await k8sApi.createNamespacedPersistentVolumeClaim({
66+
namespace: 'default',
67+
body: volumeClaim
6168
})
6269

63-
afterAll(async () => {
64-
if (rwxPvcName) {
65-
try {
66-
await k8sApi.deleteNamespacedPersistentVolumeClaim({
67-
name: rwxPvcName,
68-
namespace: 'default'
69-
})
70-
} catch {
71-
// Ignore cleanup errors - PVC may not exist
72-
}
73-
}
74-
})
70+
process.env.ACTIONS_RUNNER_CLAIM_NAME = rwxPvcName
7571

76-
afterEach(async () => {
77-
await testHelper.cleanup()
78-
})
72+
prepareJobData = testHelper.getPrepareJobDefinition()
73+
prepareJobOutputFilePath = testHelper.createFile('prepare-job-output.json')
74+
})
7975

80-
it('should successfully run hook flow with RWX volume', async () => {
81-
await expect(
82-
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
83-
).resolves.not.toThrow()
76+
afterEach(async () => {
77+
await testHelper.cleanup()
78+
delete process.env.ACTIONS_RUNNER_CLAIM_NAME
79+
await k8sApi
80+
.deleteNamespacedPersistentVolumeClaim({
81+
name: rwxPvcName,
82+
namespace: 'default',
83+
gracePeriodSeconds: 0
84+
})
85+
.catch(() => undefined)
86+
await k8sApi.deletePersistentVolume({ name: rwxPvName }).catch(() => undefined)
87+
await k8sStorageApi
88+
.deleteStorageClass({ name: rwxStorageClassName })
89+
.catch(() => undefined)
90+
})
8491

85-
const prepareJobOutputJson = fs.readFileSync(prepareJobOutputFilePath)
86-
const prepareJobOutputData = JSON.parse(prepareJobOutputJson.toString())
92+
it('should successfully run hook flow with RWX volume', async () => {
93+
await expect(
94+
prepareJob(prepareJobData.args, prepareJobOutputFilePath)
95+
).resolves.not.toThrow()
8796

88-
const scriptStepData = testHelper.getRunScriptStepDefinition()
97+
const prepareJobOutputJson = fs.readFileSync(prepareJobOutputFilePath)
98+
const prepareJobOutputData = JSON.parse(prepareJobOutputJson.toString())
8999

90-
await expect(
91-
runScriptStep(
92-
scriptStepData.args as RunScriptStepArgs,
93-
prepareJobOutputData.state
94-
)
95-
).resolves.not.toThrow()
100+
const scriptStepData = testHelper.getRunScriptStepDefinition()
96101

97-
await expect(cleanupJob()).resolves.not.toThrow()
98-
})
102+
await expect(
103+
runScriptStep(
104+
scriptStepData.args as RunScriptStepArgs,
105+
prepareJobOutputData.state
106+
)
107+
).resolves.not.toThrow()
99108

100-
it('should verify RWX PVC was created with correct access mode', async () => {
101-
const pvc = await k8sApi.readNamespacedPersistentVolumeClaim({
102-
name: rwxPvcName,
103-
namespace: 'default'
104-
})
109+
await expect(cleanupJob()).resolves.not.toThrow()
110+
})
105111

106-
expect(pvc.spec?.accessModes).toContain('ReadWriteMany')
107-
expect(pvc.spec?.storageClassName).toBe(getRWXStorageClass())
108-
expect(pvc.spec?.volumeMode).toBe('Filesystem')
112+
it('should verify RWX PVC was created with correct access mode', async () => {
113+
const pvc = await k8sApi.readNamespacedPersistentVolumeClaim({
114+
name: rwxPvcName,
115+
namespace: 'default'
109116
})
110117

111-
it('should verify RWX claim name is set correctly', () => {
112-
expect(process.env.ACTIONS_RUNNER_CLAIM_NAME).toBe(rwxPvcName)
113-
})
118+
expect(pvc.spec?.accessModes).toContain('ReadWriteMany')
119+
expect(pvc.spec?.storageClassName).toBe(rwxStorageClassName)
120+
expect(pvc.spec?.volumeMode).toBe('Filesystem')
114121
})
115122

116-
if (!isRWXTestEnabled()) {
117-
it(RWX_SKIP_MESSAGE, () => {})
118-
}
123+
it('should verify RWX claim name is set correctly', () => {
124+
expect(process.env.ACTIONS_RUNNER_CLAIM_NAME).toBe(rwxPvcName)
125+
})
119126
})

0 commit comments

Comments
 (0)