Skip to content

Commit 40f6cab

Browse files
authored
Merge pull request #994 from snyk/feat/cronjobs
[RUN-1518] Add support for CronJobs in batch/v1 API
2 parents 2143394 + 44f7eca commit 40f6cab

File tree

9 files changed

+97
-29
lines changed

9 files changed

+97
-29
lines changed

src/supervisor/types.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { IncomingMessage } from 'http';
22
import {
33
AppsV1Api,
44
BatchV1Api,
5-
BatchV1beta1Api,
65
CoreV1Api,
76
CustomObjectsApi,
87
KubeConfig,
@@ -41,28 +40,20 @@ export interface IK8sClients {
4140
readonly appsClient: AppsV1Api;
4241
readonly coreClient: CoreV1Api;
4342
readonly batchClient: BatchV1Api;
44-
readonly batchUnstableClient: BatchV1beta1Api;
4543
readonly customObjectsClient: CustomObjectsApi;
4644
}
4745

4846
export class K8sClients implements IK8sClients {
4947
public readonly appsClient: AppsV1Api;
5048
public readonly coreClient: CoreV1Api;
5149
public readonly batchClient: BatchV1Api;
52-
// TODO: Keep an eye on this! We need v1beta1 API for CronJobs.
53-
// https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-versioning
54-
// CronJobs will appear in v2 API, but for now there' only v2alpha1, so it's a bad idea to use it.
55-
// TODO: https://kubernetes.io/blog/2021/04/09/kubernetes-release-1.21-cronjob-ga/
56-
// CronJobs are now GA in Kubernetes 1.21 in the batch/v1 API, we should add support for it!
57-
public readonly batchUnstableClient: BatchV1beta1Api;
5850
/** This client is used to access Custom Resources in the cluster, e.g. DeploymentConfig on OpenShift. */
5951
public readonly customObjectsClient: CustomObjectsApi;
6052

6153
constructor(config: KubeConfig) {
6254
this.appsClient = config.makeApiClient(AppsV1Api);
6355
this.coreClient = config.makeApiClient(CoreV1Api);
6456
this.batchClient = config.makeApiClient(BatchV1Api);
65-
this.batchUnstableClient = config.makeApiClient(BatchV1beta1Api);
6657
this.customObjectsClient = config.makeApiClient(CustomObjectsApi);
6758
}
6859
}

src/supervisor/watchers/handlers/cron-job.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { V1beta1CronJob, V1beta1CronJobList } from '@kubernetes/client-node';
1+
import { V1CronJob, V1CronJobList } from '@kubernetes/client-node';
22
import { deleteWorkload, trimWorkload } from './workload';
33
import { WorkloadKind } from '../../types';
44
import { FALSY_WORKLOAD_NAME_MARKER } from './types';
@@ -13,25 +13,21 @@ import {
1313

1414
export async function paginatedCronJobList(namespace: string): Promise<{
1515
response: IncomingMessage;
16-
body: V1beta1CronJobList;
16+
body: V1CronJobList;
1717
}> {
18-
const v1CronJobList = new V1beta1CronJobList();
19-
v1CronJobList.apiVersion = 'batch/v1beta1';
18+
const v1CronJobList = new V1CronJobList();
19+
v1CronJobList.apiVersion = 'batch/v1';
2020
v1CronJobList.kind = 'CronJobList';
21-
v1CronJobList.items = new Array<V1beta1CronJob>();
21+
v1CronJobList.items = new Array<V1CronJob>();
2222

2323
return await paginatedList(
2424
namespace,
2525
v1CronJobList,
26-
k8sApi.batchUnstableClient.listNamespacedCronJob.bind(
27-
k8sApi.batchUnstableClient,
28-
),
26+
k8sApi.batchClient.listNamespacedCronJob.bind(k8sApi.batchClient),
2927
);
3028
}
3129

32-
export async function cronJobWatchHandler(
33-
cronJob: V1beta1CronJob,
34-
): Promise<void> {
30+
export async function cronJobWatchHandler(cronJob: V1CronJob): Promise<void> {
3531
cronJob = trimWorkload(cronJob);
3632

3733
if (

src/supervisor/watchers/handlers/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
6969
paginatedReplicationControllerList(namespace),
7070
},
7171
[WorkloadKind.CronJob]: {
72-
endpoint: '/apis/batch/v1beta1/watch/namespaces/{namespace}/cronjobs',
72+
endpoint: '/apis/batch/v1/watch/namespaces/{namespace}/cronjobs',
7373
handlers: {
7474
[DELETE]: cronJobWatchHandler,
7575
},

src/supervisor/workload-reader.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,10 @@ const jobReader: IWorkloadReaderFunc = async (workloadName, namespace) => {
206206
return metadata;
207207
};
208208

209-
// Keep an eye on this! We need v1beta1 API for CronJobs.
210-
// https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-versioning
211-
// CronJobs will appear in v2 API, but for now there' only v2alpha1, so it's a bad idea to use it.
209+
// cronJobReader can read v1 and v1beta1 CronJobs
212210
const cronJobReader: IWorkloadReaderFunc = async (workloadName, namespace) => {
213211
const cronJobResult = await kubernetesApiWrappers.retryKubernetesApiRequest(
214-
() =>
215-
k8sApi.batchUnstableClient.readNamespacedCronJob(workloadName, namespace),
212+
() => k8sApi.batchClient.readNamespacedCronJob(workloadName, namespace),
216213
);
217214
const cronJob = trimWorkload(cronJobResult.body);
218215

test/fixtures/cronjob-v1beta1.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
apiVersion: batch/v1beta1
2+
kind: CronJob
3+
metadata:
4+
name: cron-job-v1beta1
5+
namespace: services
6+
labels:
7+
app.kubernetes.io/name: cron-job-v1beta1
8+
spec:
9+
schedule: '* * * * *'
10+
jobTemplate:
11+
spec:
12+
template:
13+
metadata:
14+
labels:
15+
app.kubernetes.io/name: cron-job-v1beta1
16+
spec:
17+
containers:
18+
- name: cron-job-v1beta1
19+
image: busybox
20+
imagePullPolicy: IfNotPresent
21+
command: ['/bin/sleep']
22+
args: ['180']
23+
restartPolicy: OnFailure

test/fixtures/cronjob.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
apiVersion: batch/v1
2+
kind: CronJob
3+
metadata:
4+
name: cron-job
5+
namespace: services
6+
labels:
7+
app.kubernetes.io/name: cron-job
8+
spec:
9+
schedule: '* * * * *'
10+
jobTemplate:
11+
spec:
12+
template:
13+
metadata:
14+
labels:
15+
app.kubernetes.io/name: cron-job
16+
spec:
17+
containers:
18+
- name: cron-job
19+
image: busybox
20+
imagePullPolicy: IfNotPresent
21+
command: ['/bin/sleep']
22+
args: ['180']
23+
restartPolicy: OnFailure

test/helpers/kubectl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export async function waitForDeployment(
231231
namespace: string,
232232
): Promise<void> {
233233
console.log(`Trying to find deployment ${name} in namespace ${namespace}`);
234-
for (let attempt = 0; attempt < 120; attempt++) {
234+
for (let attempt = 0; attempt < 180; attempt++) {
235235
try {
236236
await exec(`./kubectl get deployment.apps/${name} -n ${namespace}`);
237237
} catch (error) {

test/integration/kubernetes.spec.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ test('deploy sample workloads', async () => {
5050
kubectl.applyK8sYaml('./test/fixtures/centos-deployment.yaml'),
5151
kubectl.applyK8sYaml('./test/fixtures/scratch-deployment.yaml'),
5252
kubectl.applyK8sYaml('./test/fixtures/consul-deployment.yaml'),
53+
kubectl.applyK8sYaml('./test/fixtures/cronjob.yaml'),
54+
kubectl.applyK8sYaml('./test/fixtures/cronjob-v1beta1.yaml'),
5355
kubectl.createPodFromImage(
5456
'alpine-from-sha',
5557
someImageWithSha,
@@ -112,7 +114,6 @@ test('snyk-monitor sends data to kubernetes-upstream', async () => {
112114
const validatorFn: WorkloadLocatorValidator = (workloads) => {
113115
return (
114116
workloads !== undefined &&
115-
workloads.length === 8 &&
116117
workloads.find(
117118
(workload) =>
118119
workload.name === 'alpine' && workload.type === WorkloadKind.Pod,
@@ -150,6 +151,16 @@ test('snyk-monitor sends data to kubernetes-upstream', async () => {
150151
(workload) =>
151152
workload.name === 'consul' &&
152153
workload.type === WorkloadKind.Deployment,
154+
) !== undefined &&
155+
workloads.find(
156+
(workload) =>
157+
workload.name === 'cron-job' &&
158+
workload.type === WorkloadKind.CronJob,
159+
) !== undefined &&
160+
workloads.find(
161+
(workload) =>
162+
workload.name === 'cron-job-v1beta1' &&
163+
workload.type === WorkloadKind.CronJob,
153164
) !== undefined
154165
);
155166
};
@@ -226,6 +237,33 @@ test('snyk-monitor sends data to kubernetes-upstream', async () => {
226237
target: { image: 'docker-image|docker.io/snyk/runtime-fixtures' },
227238
},
228239
]);
240+
241+
const scanResultsCronJob = await getUpstreamResponseBody(
242+
`api/v1/scan-results/${integrationId}/Default%20cluster/services/CronJob/cron-job`,
243+
);
244+
expect(scanResultsCronJob.workloadScanResults['busybox']).toEqual<
245+
ScanResult[]
246+
>([
247+
{
248+
identity: { type: 'linux', args: { platform: 'linux/amd64' } },
249+
facts: expect.any(Array),
250+
target: { image: 'docker-image|busybox' },
251+
},
252+
]);
253+
254+
// the v1 reader works for both v1 and v1beta1
255+
const scanResultsCronJobBeta = await getUpstreamResponseBody(
256+
`api/v1/scan-results/${integrationId}/Default%20cluster/services/CronJob/cron-job-v1beta1`,
257+
);
258+
expect(scanResultsCronJobBeta.workloadScanResults['busybox']).toEqual<
259+
ScanResult[]
260+
>([
261+
{
262+
identity: { type: 'linux', args: { platform: 'linux/amd64' } },
263+
facts: expect.any(Array),
264+
target: { image: 'docker-image|busybox' },
265+
},
266+
]);
229267
});
230268

231269
test('snyk-monitor sends binary hashes to kubernetes-upstream after adding another deployment', async () => {

test/setup/platforms/kind.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const clusterName = 'kind';
88

99
export async function setupTester(): Promise<void> {
1010
const osDistro = platform();
11-
await download(osDistro, 'v0.8.1');
11+
await download(osDistro, 'v0.11.1');
1212
}
1313

1414
export async function createCluster(version: string): Promise<void> {

0 commit comments

Comments
 (0)