🌳
pt0/deployF/k8sF/kanikoF/serializeKanikoBuildsAI.mts
1import { BatchV1Api, type V1Job } from '@kubernetes/client-node'
2import { getKubeApis } from '../getApisF.mts'
7const listActiveKanikoJobs = async ({batchApi, cluster_name}: {batchApi: BatchV1Api, cluster_name: string}): Promise<V1Job[]> => {
8 const {items: jobs} = await batchApi.listNamespacedJob({namespace: 'default', labelSelector: `cluster_name=${cluster_name}`})
9 return jobs.filter(j => !!j.metadata?.name?.startsWith('kaniko2-') && !j.status?.succeeded && !j.status?.failed)
12export const serializeKanikoBuilds = async ({newJob, cluster_name, git_sha, logStatus}: {
13 newJob: V1Job, cluster_name: string, git_sha: string, logStatus: (status: string) => void,
14}) => {
15 const jobName = newJob.metadata?.name
16 assertDefined(jobName)
17 const rntIdx = jobName.lastIndexOf('-rnt-')
18 const appPrefix = rntIdx === -1 ? jobName : jobName.slice(0, rntIdx + 5)
19 const {kubeConfig} = await getKubeApis({cluster_name})
20 const batchApi = kubeConfig.makeApiClient(BatchV1Api)
21 const activeA = await listActiveKanikoJobs({batchApi, cluster_name})
22 const staleSameAppA = activeA.filter(j => {
23 const jName = j.metadata?.name
24 return !!jName && jName !== jobName && jName.startsWith(appPrefix) && j.metadata?.labels?.git_sha !== git_sha
25 })
26 for (const j of staleSameAppA) {
27 const staleJobName = j.metadata!.name!
28 logStatus(`canceling superseded ${staleJobName}`)
29 await batchApi.deleteNamespacedJob({name: staleJobName, namespace: 'default', propagationPolicy: 'Background'})
30 }
31 const otherNamesA = activeA.filter(j => {
32 const jName = j.metadata?.name
33 return !!jName && jName !== jobName && !jName.startsWith(appPrefix) && j.metadata?.labels?.git_sha !== git_sha
34 }).map(j => j.metadata!.name!)
35 if (!otherNamesA.length) return
36 const waitDeadlineMs = Date.now() + defaultJobTimeoutMs
37 let remainingA = otherNamesA
38 while (remainingA.length && Date.now() < waitDeadlineMs) {
39 logStatus(`waiting for ${remainingA.length} kaniko build(s)`)
40 await sleep(10_000)
41 const activeNowA = await listActiveKanikoJobs({batchApi, cluster_name})
42 const activeNamesS = new Set(activeNowA.map(j => j.metadata?.name))
43 remainingA = remainingA.filter(n => activeNamesS.has(n))
44 }
45 // fail-open: proceed despite active builds — RWX PVC backstop prevents hard failure
46 if (remainingA.length) console.log(`proceeding despite ${remainingA.length} active kaniko build(s) (wait timeout)`)