1import * as _ from 'lodash-es' 2import { DateTime } from 'luxon' 3import { type V1Volume, type V1VolumeMount, type V1Container, type V1Pod, type V1Job, type V1ConfigMap } from '@kubernetes/client-node' 48 patchedFilesH?: Record<string, string> 49 wpManReplaceTokH?: Record<string, string> 52type KanikoCtxStore = { 53 appliedShared: Set<string> 54 gitChecked: Set<string> 55 localCtxTarPath?: string 61const tarspecsMaxBytes = 1024 * 1024 // 1MB ConfigMap limit 64const getKanikoCtx = () => { 65 if (!kanikoCtx.getStore()) kanikoCtx.enterWith({appliedShared: new Set(), gitChecked: new Set()}) // ctx:clear 66 return kanikoCtx.getStore()! 69const kanikoWorkspaceName = 'kanwork', kanikoWorkspacePath = `/${kanikoWorkspaceName}`, kanCacheDir = '/kanikocache' 70const kanikoWorkspaceVol: V1Volume = {emptyDir: {}, name: kanikoWorkspaceName} 71const kanikoWorkspaceMount: V1VolumeMount = {mountPath: kanikoWorkspacePath, name: kanikoWorkspaceName} 73const getGitInitCmd = ({gitRemoteUrl, git_sha}: {gitRemoteUrl: string, git_sha?: string}) => { 74 const fetchCmd = git_sha ? `git fetch origin ${git_sha}` : 'git fetch origin' 75 return `git init && git config remote.origin.url >&- || git remote add origin ${gitRemoteUrl} && ${fetchCmd}` 78type CiContainersSshProps = { 81 gitsshRepoPath: string 82 sshKeySecretName?: string 85type CiContainersResult = {initContainers: V1Container[], volumes: V1Volume[], volumeMounts: V1VolumeMount[]} 87const ciContainersSsh = ({git_sha, gitsshHost, gitsshRepoPath, sshKeySecretName = 'codeserv-ssh'}: CiContainersSshProps): CiContainersResult => { 88 const sshKeyVolName = 'ssh-key' 90 {name: sshKeyVolName, secret: {secretName: sshKeySecretName, defaultMode: 0o600}}, 95 'cp /ssh-key/id_ed25519 ~/.ssh/id_ed25519', 96 'chmod 600 ~/.ssh/id_ed25519', 97 `ssh-keyscan -p 22 "${gitsshHost}" >> ~/.ssh/known_hosts 2>/dev/null || true`, 99 const gitUrl = `ssh://git@${gitsshHost}${gitsshRepoPath}` 100 let cmd = `${sshSetup} && cd ${kanikoWorkspacePath} && ${getGitInitCmd({gitRemoteUrl: gitUrl, git_sha})}` 101 if (git_sha) cmd += ` && git reset --hard ${git_sha}` 102 const initContainers = [{ 103 command: ['/bin/sh', '-c'], args: [cmd], image: 'alpine/git:latest', imagePullPolicy: 'Always', name: 'gitclone', 104 volumeMounts: [kanikoWorkspaceMount, {mountPath: '/ssh-key', name: sshKeyVolName, readOnly: true}] 106 return {initContainers, volumes, volumeMounts: [kanikoWorkspaceMount]} 109type CiContainersHttpsProps = { 112 gitsshRepoPath: string 115const ciContainersHttps = ({git_sha, gitsshHost, gitsshRepoPath}: CiContainersHttpsProps): CiContainersResult => { 116 const repoName = gitsshRepoPath.replace(/^\/home\/git\/repos\//, '').replace(/\.git$/, '') 117 const tokenVolName = 'http-token' 119 {name: tokenVolName, secret: {secretName: `${gitsshHost}-httptoken`}}, 122 const gitUrl = `http://git:$(cat /token/token)@${gitsshHost}-http/git/${repoName}.git` 123 let cmd = `cd ${kanikoWorkspacePath} && ${getGitInitCmd({gitRemoteUrl: gitUrl, git_sha})}` 124 if (git_sha) cmd += ` && git reset --hard ${git_sha}` 125 const initContainers = [{ 126 command: ['/bin/sh', '-c'], args: [cmd], image: 'alpine/git:latest', imagePullPolicy: 'Always', name: 'gitclone', 127 volumeMounts: [kanikoWorkspaceMount, {mountPath: '/token', name: tokenVolName, readOnly: true}] 129 return {initContainers, volumes, volumeMounts: [kanikoWorkspaceMount]} 132type CiContainersProps = { 134 gitRemoteSecretName: string 135 gitRemoteKubeSecretName: string 138const ciContainers = ({git_sha, gitRemoteSecretName, gitRemoteKubeSecretName}: CiContainersProps): CiContainersResult => { 140 {name: gitRemoteKubeSecretName, secret: {secretName: gitRemoteKubeSecretName}}, 143 let cmd = `cd ${kanikoWorkspacePath} && ${getGitInitCmd({gitRemoteUrl: `$(cat /secrets2/${gitRemoteSecretName})`, git_sha})}` 144 if (git_sha) cmd += ` && git reset --hard ${git_sha}` 145 const initContainers = [{ 146 command: ['/bin/sh', '-c'], args: [cmd], image: 'alpine/git:latest', imagePullPolicy: 'Always', name: 'gitclone', 147 volumeMounts: [kanikoWorkspaceMount, {mountPath: '/secrets2', name: gitRemoteKubeSecretName}] 149 return {initContainers, volumes, volumeMounts: [kanikoWorkspaceMount]} 152const ciContainersLocalCtx = (): CiContainersResult => { 153 const waitctx: V1Container = {name: 'waitctx', image: 'busybox:latest', command: ['/bin/sh', '-c'], args: [`while [ ! -f ${kanikoWorkspacePath}/.ctxready ]; do sleep 2; done`], volumeMounts: [kanikoWorkspaceMount]} 154 const extractctx: V1Container = {name: 'extractctx', image: 'busybox:latest', command: ['/bin/sh', '-c'], args: [`cd ${kanikoWorkspacePath} && tar -xzf localctx.tar.gz && rm -f localctx.tar.gz`], volumeMounts: [kanikoWorkspaceMount]} 155 return {initContainers: [waitctx, extractctx], volumes: [kanikoWorkspaceVol], volumeMounts: [kanikoWorkspaceMount]} 158type KanikoBuildJobTmplProps = { 160 repo_name_tag: string 161 gitRemoteKubeSecretName?: string 162 gitRemoteSecretName?: string 164 isStandalone?: boolean 166 dockerfileCfgMap: V1ConfigMap 167 tarspecsCfgMapName?: string 169 gitsshRepoPath?: string 170 gitsshHttpsHostname?: string 173const kanikoBuildJobTmpl = ({name, repo_name_tag, gitRemoteKubeSecretName, gitRemoteSecretName, df_sha, isStandalone, pvcName, dockerfileCfgMap, tarspecsCfgMapName, gitsshHost, gitsshRepoPath, gitsshHttpsHostname}: KanikoBuildJobTmplProps): V1Job => { 174 const {cluster_name, dockreg_host, git_sha} = getKanikoCtx() 176 const sshKeySecretName = isOcWeb ? process.env.GITSSH_SSH_SECRET : undefined 177 const jobName = `kaniko2-${name}` 178 const jobBase = {apiVersion: 'batch/v1', kind: 'Job', metadata: {name: jobName, namespace: 'default', labels: {cluster_name}}} 179 const noCloneCi: CiContainersResult = {initContainers: [] as V1Container[], volumes: [kanikoWorkspaceVol], volumeMounts: [kanikoWorkspaceMount]} 180 const kanikoLocalContext = getAppCfg()?.kanikoLocalContext ?? true 181 const ci = kanikoLocalContext ? (tarspecsCfgMapName ? ciContainersLocalCtx() : noCloneCi) : isStandalone ? noCloneCi : (gitsshHttpsHostname ? ciContainersHttps({git_sha, gitsshHost: gitsshHost!, gitsshRepoPath: gitsshRepoPath!}) : gitsshHost ? ciContainersSsh({git_sha, gitsshHost, gitsshRepoPath: gitsshRepoPath!, ...(sshKeySecretName && {sshKeySecretName})}) : ciContainers({git_sha, gitRemoteSecretName: gitRemoteSecretName!, gitRemoteKubeSecretName: gitRemoteKubeSecretName!})) 183 let {initContainers} = ci 184 const volumes = [...ci.volumes, ...pvc.volumes] 185 const volumeMounts = [...ci.volumeMounts, ...pvc.volumeMounts] 188 const nextBuildHeapMb = getAppCfg()?.nextBuildHeapMb || 2048 189 const kanikoCpuLimit = getAppCfg()?.kanikoCpuLimit || '2' 190 const nextCachePath = appPath ? `/approot/${appPath}/.next/cache` : undefined 191 const nextCacheSubPath = appPath ? `next-cache-${appPath.replace(/\//g, '-')}` : undefined 193 if (tarspecsCfgMapName) { 200 command: ['/bin/sh', '-c'], 202 resources: {requests: {memory: '2Gi', cpu: '500m'}, limits: {memory: '4Gi', cpu: '2'}}, 203 env: [{name: 'TAR_SPECS_PATH', value: '/tarspecs/tarspecs.json'}, {name: 'PNPM_HOME', value: '/pnpm'}, {name: 'HOME', value: '/pnpm'}, {name: 'PATH', value: '/pnpm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'}, ...((getAppCfg() as Record<string, unknown>)?.forceIpv4Dns ? [{name: 'NODE_OPTIONS', value: '--dns-result-order=ipv4first'}] : [])], 204 volumeMounts: [kanikoWorkspaceMount, {name: pvcName, mountPath: '/pnpm/store', subPath: 'pnpm-store'}, {mountPath: '/tarspecs', name: 'tarspecs-cfg', readOnly: true}], 207 volumes.push({name: 'tarspecs-cfg', configMap: {name: tarspecsCfgMapName}}) 210 return _.merge(jobBase, { 211 metadata: {labels: {cluster_name, git_sha, df_sha}}, 213 ttlSecondsAfterFinished: 60 * 60 * 2, 214 activeDeadlineSeconds: Math.floor(defaultJobTimeoutMs / 1000) - 60, // <client wait so k8s DeadlineExceeded surfaces; auto-fails stuck jobs for TTL cleanup 217 metadata: {labels: {name, git_sha}}, 219 restartPolicy: 'Never', 222 // image: 'gcr.io/kaniko-project/executor:v1.10.0', 223 image: 'ghcr.io/kaniko-build/dist/chainguard-forks-kaniko/executor:v1.25.11-debug', 225 `--dockerfile=/dockerfilecfg/Dockerfile`, `--context=dir://${kanikoWorkspacePath}`, `--destination=${repo_name_tag}`, 226 `--cache=true`, `--cache-dir=${kanCacheDir}`, `--snapshot-mode=redo`, `--use-new-run`, `--ignore-path=/pnpm/store`, 227 nextCachePath && `--ignore-path=${nextCachePath}`, cluster_name === 'minik' && '--force' 230 resources: {requests: {memory: `${nextBuildHeapMb}Mi`, cpu: '500m'}, limits: {memory: `${nextBuildHeapMb + 2048}Mi`, cpu: kanikoCpuLimit}}, 231 volumeMounts: [...volumeMounts, {name: pvcName, mountPath: '/pnpm/store', subPath: 'pnpm-store'}, 232 ...(nextCachePath ? [{name: pvcName, mountPath: nextCachePath, subPath: nextCacheSubPath!}] : []), 233 {name: 'dockerfilecfg', mountPath: '/dockerfilecfg'}, {mountPath: '/kaniko/.docker', name: 'dockerconfigvol'}] 237 {name: 'dockerfilecfg', configMap: {name: dockerfileCfgMap.metadata?.name}}, 238 {name: 'dockerconfigvol', secret: {items: [{key: '.dockerconfigjson', path: 'config.json'}], secretName: regCredSecName({dockreg_host})}} 246const getGitRepoVars = ({git_repo_name}: {git_repo_name: string}) => { 247 const gitRemoteSecretName = `git_remote_${git_repo_name}` as secretNameType 249 const {metadata: {name: gitRemoteKubeSecretName}} = gitRemoteSecret 250 return {gitRemoteSecretName, gitRemoteKubeSecretName, gitRemoteSecret} 253const findMatchingPods = async (cluster_name: string, cb: (pod: V1Pod) => boolean) => (await get1ClusterPods({cluster_name}) as V1Pod[]).filter(cb) 255const gitshaPath = ['metadata', 'labels', 'git_sha'] 257const getLatestPod = async ({cluster_name, name, git_sha}: {cluster_name: string, name: string, git_sha: string}) => { 258 let jobPods = await findMatchingPods(cluster_name, (podH) => { 259 const podName = podH.metadata?.name 260 if (!podName?.includes(name)) return false 261 return git_sha == _.get(podH, gitshaPath) 263 jobPods = _.chain(jobPods).sortBy((pod) => { 264 const ts = pod.metadata?.creationTimestamp 265 return DateTime.fromISO(typeof ts === 'string' ? ts : ts?.toISOString() || '') 270const getKanikoPod = async ({newJob, cluster_name}: {newJob: V1Job, cluster_name: string}) => { 271 const pods = await findMatchingPods(cluster_name, (podH) => { 272 const podName = podH.metadata?.name 273 if (!podName?.includes(newJob.metadata?.name || '')) return false 274 if (_.get(newJob, gitshaPath) != _.get(podH, gitshaPath)) return false 275 const containerStatuses = podH.status?.containerStatuses 276 return !!containerStatuses?.find(({name, state}) => name === 'kaniko' && (state?.running || state?.terminated)) 281const getFailedContainerLogs = async ({cluster_name, podName, pod}: {cluster_name: string, podName: string, pod: V1Pod}): Promise<{logs: string, oomKilledHint?: string}> => { 282 const allStatuses = [...(pod.status?.initContainerStatuses || []), ...(pod.status?.containerStatuses || [])] 283 const failedContainers = allStatuses.filter(s => s.state?.terminated?.exitCode !== 0 && s.state?.terminated) 284 let allLogs = '', oomKilledHint: string | undefined 285 for (const container of failedContainers) { 286 const containerName = container.name! 287 const oomHint = getOomKilledHint({terminatedReason: container.state?.terminated?.reason, containerName}) 289 oomKilledHint = oomHint 292 console.log(`\n--- ${containerName} logs (${podName}) ---`) 293 const result = await noOutCmdCtx.run({}, () => eptKubeCli([cluster_name, 'logs', podName, '-c', containerName, '--tail=150'])) 294 const logs = (result as {stdout?: string})?.stdout || '(empty)' 296 allLogs += logs + '\n' 299 if (parsed) console.log(chalkRed(`\n>>> ${parsed.hint}`)) 301 return {logs: allLogs, oomKilledHint} 304const guardJobFailureStatus = async ({jobH, cluster_name}: {jobH: V1Job, cluster_name: string}) => { 305 const {succeeded, conditions} = jobH.status || {} 306 if (succeeded) return {succeeded: !!succeeded} 307 const failureStatus = conditions?.find(({type}) => type == 'Failed') 310 const jobName = jobH.metadata?.name 311 const pods = await findMatchingPods(cluster_name, (p) => !!p.metadata?.name?.includes(jobName || '')) 312 const failedPod = pods.find((p) => p.status?.phase === 'Failed') || pods[0] 313 let logs = '', oomKilledHint: string | undefined 314 if (failedPod?.metadata?.name) ({logs, oomKilledHint} = await getFailedContainerLogs({cluster_name, podName: failedPod.metadata.name, pod: failedPod})) 320const checkKanikoStatus = async ({newJob}: {newJob: V1Job}) => { 321 const {cluster_name} = getKanikoCtx() 323 return guardJobFailureStatus({jobH: await read2Resource({resource: newJob as KubeResource, cluster_name}) as V1Job, cluster_name}) 326type GetKanikoResourcesProps = { 328 dockerfileContent: string 329 tarSpecsA?: TarSpec[] 331 isStandalone?: boolean 332 repo_name_tag: string 335 gitsshRepoPath?: string 336 gitsshHttpsHostname?: string 339export const getKanikoResources = async ({name, dockerfileContent, tarSpecsA, df_sha, isStandalone, repo_name_tag, action, gitsshHost, gitsshRepoPath, gitsshHttpsHostname}: GetKanikoResourcesProps) => { 340 const {cluster_name} = getKanikoCtx() 343 const kanikoLocalContext = getAppCfg()?.kanikoLocalContext ?? true 344 const dockerfileCfgMap: V1ConfigMap = {apiVersion: 'v1', kind: 'ConfigMap', metadata: {name, labels: {cluster_name}}, data: {Dockerfile: dockerfileContent}} 345 const useSsh = !!gitsshHost, useGitsshHttps = !!gitsshHttpsHostname 346 let gitRemoteKubeSecretName: string | undefined, gitRemoteSecretName: secretNameType | undefined, gitRemoteSecret: any 347 if (!useSsh && !useGitsshHttps && !isStandalone) { 348 ;({gitRemoteKubeSecretName, gitRemoteSecretName, gitRemoteSecret} = getGitRepoVars({git_repo_name: git_repo_name!})) 350 const pvcName = 'kanikocache-' + (kanikoLocalContext ? 'localctx' : isStandalone ? 'standalone' : useGitsshHttps ? 'gitssh-https' : useSsh ? 'gitssh' : git_repo_name) 352 let tarspecsCfgMapName: string | undefined 353 const resources: any[] = [dockerfileCfgMap] 354 if (tarSpecsA?.length) { 355 tarspecsCfgMapName = `${name}-tarspecs` 356 const tarspecsJson = JSON.stringify({tarSpecsA}) 357 if (tarspecsJson.length > tarspecsMaxBytes) throwDebugH({tarspecsBytes: tarspecsJson.length, tarspecsMaxBytes}) 358 const tarspecsCfgMap = {apiVersion: 'v1', kind: 'ConfigMap', metadata: {name: tarspecsCfgMapName, labels: {cluster_name}}, data: {'tarspecs.json': tarspecsJson}} 359 resources.push(tarspecsCfgMap) 362 const newJob = kanikoBuildJobTmpl({name, repo_name_tag, gitRemoteKubeSecretName, gitRemoteSecretName: gitRemoteSecretName as string | undefined, df_sha, isStandalone, dockerfileCfgMap, tarspecsCfgMapName, pvcName, gitsshHost, gitsshRepoPath, gitsshHttpsHostname}) 363 resources.push(...await genericPvcTmplA({cluster_name, name: pvcName, sizeGb: 200, useM2: true, action, accessModes: ['ReadWriteMany']})) 364 return {newJob, gitRemoteSecret, resources, useSsh} 367const getPodPhase = (pod: any) => pod?.status?.phase || 'Unknown' 369const kanikoRetryable = async ({newJob, dfLabel, repo_name_tag}: {newJob: V1Job, dfLabel: string, repo_name_tag: string}) => { 371 const name = newJob.metadata.name, git_sha = _.get(newJob, gitshaPath) 372 const {localCtxTarPath, cluster_name} = getKanikoCtx() 374 const maxRetries = 120, pollMs = 10_000 377 let pendingStartTime: number | null = null, lastPendingLog: string | null = null 378 let ctxUploaded = false 381 const throwKanikoBuildFailed = async ({failReason, delStaleJobHint}: {failReason?: string, delStaleJobHint?: boolean}): Promise<never> => { 382 console.log(`\n--- kaniko build failed ---`) 383 const pods = await findMatchingPods(cluster_name, p => !!p.metadata?.name?.includes(name)) 384 const failedPod = pods.find(p => p.status?.phase === 'Failed') || pods[0] 385 let logs = '', oomKilledHint: string | undefined 386 if (failedPod?.metadata?.name) ({logs, oomKilledHint} = await getFailedContainerLogs({cluster_name, podName: failedPod.metadata.name, pod: failedPod})) 387 if (!oomKilledHint && delStaleJobHint) console.error(chalkRed(`\n>>> kaniko job failed (${failReason || 'unknown'}). Re-run with --delStaleJob to delete and retry.`)) 388 throwDebugH({name, reason: oomKilledHint ? 'OOMKilled during kaniko build' : 'kaniko build failed', failReason, oomKilledHint, logs}) 391 for (let i = 0; i < maxRetries; i++) { 393 const existingJob = await read2Resource({resource: newJob as KubeResource, cluster_name}) as V1Job | undefined 395 const dfShaMismatch = _.get(existingJob, 'metadata.labels.df_sha') !== _.get(newJob, 'metadata.labels.df_sha') 396 const isFailed = existingJob.status?.failed 400 logStatus('deleted stale job'); await sleep(15_000); continue 402 const failCondition = existingJob.status?.conditions?.find(c => c.type === 'Failed') 403 await throwKanikoBuildFailed({failReason: failCondition?.reason, delStaleJobHint: true}) 407 logStatus('expired'); await sleep(15_000); continue 412 const resp = await objectApiCall({resource: newJob as KubeResource, objectApiMethod: 'create', cluster_name}) 413 if (resp?.kind === 'Job') logStatus('created') 414 else if (resp?.kind === 'Status') { console.error(resp); throwDebugH({resp, context: 'kaniko job create'}) } 415 else if (resp) console.error('kaniko create error', resp) 419 const {succeeded} = await guardJobFailureStatus({jobH: existingJob, cluster_name}) 420 if (succeeded) { console.log(' done'); return } 422 const latestPod = await getLatestPod({cluster_name, git_sha, name}) 423 const phase = getPodPhase(latestPod) 424 if (phase === 'Failed') { logStatus('Failed'); throwDebugH({podName: latestPod?.metadata?.name}) } 426 if (localCtxTarPath && !ctxUploaded) { 427 const waitctxPods = await findMatchingPods(cluster_name, p => { 428 if (!p.metadata?.name?.includes(name)) return false 429 return !!p.status?.initContainerStatuses?.find(s => s.name === 'waitctx' && s.state?.running) 431 if (waitctxPods[0]?.metadata?.name) { 432 logStatus('uploading ctx') 433 await uploadCtxToPod({cluster_name, podName: waitctxPods[0].metadata.name, containerName: 'waitctx', ctxTarPath: localCtxTarPath}) 438 const runningPod = await getKanikoPod({newJob, cluster_name}) 442 const statusStr = initInfo ? `${phase} (${initInfo.name}${initInfo.detail ? ': ' + initInfo.detail : ' ' + initInfo.status})` : phase 444 // Only timeout if truly stuck in Pending (unschedulable) - not during normal init container execution 445 const isInitRunning = initInfo && (initInfo.status === 'running' || initInfo.detail === 'PodInitializing') 446 if (phase === 'Pending' && pendingInfo && !isInitRunning) { 447 pendingStartTime ||= Date.now() 448 const timeout = pendingInfo.isPvcPending ? pvcPendingTimeoutMs : pendingTimeoutMs 449 if (Date.now() - pendingStartTime > timeout) { 450 console.error(chalkRed(`\n${pendingInfo.message || 'Pod stuck in Pending'}`)) 451 throwDebugH({podName: latestPod?.metadata?.name, reason: pendingInfo.reason, message: pendingInfo.message}) 453 if (pendingInfo.message && pendingInfo.message !== lastPendingLog) { console.error(chalkRed(`\n${pendingInfo.message}`)); lastPendingLog = pendingInfo.message } 454 } else if (phase === 'Pending' && !pendingInfo && !isInitRunning) { 455 pendingStartTime ||= Date.now() 456 if (Date.now() - pendingStartTime > pvcPendingTimeoutMs * 5) { 457 throwDebugH({podName: latestPod?.metadata?.name, reason: 'volume attach timeout'}) 459 } else if (initInfo?.status === 'failed') { 460 console.error(chalkRed(`\ninit container ${initInfo.name} failed: ${initInfo.detail || 'unknown'}`)) 461 throwDebugH({podName: latestPod?.metadata?.name, initContainer: initInfo.name, reason: initInfo.detail}) 462 } else { pendingStartTime = null } 466 const podAge = runningPod.metadata?.creationTimestamp 467 ? Math.round((Date.now() - new Date(runningPod.metadata.creationTimestamp as unknown as string).getTime()) / 1000) 469 logStatus(podAge > 60 ? `streaming (running ${Math.round(podAge / 60)}m)` : 'building') 472 const awaitRes = await awaitJobPod({job: newJob as KubeResource, podName: runningPod.metadata.name, containerName: 'kaniko', cluster_name, skipHeader: true}) 473 if (awaitRes?.failed) await throwKanikoBuildFailed({failReason: awaitRes.failReason}) 476 const msg = err?.message || String(err) 477 if (/abort|ETIMEDOUT|ECONNRESET|ECONNREFUSED|FetchError/i.test(msg)) { 478 logStatus('transient API error, retrying') 485 const finalJob = await read2Resource({resource: newJob as KubeResource, cluster_name}) as any 486 const {succeeded} = finalJob?.status || {} 487 if (succeeded) { console.log(' done'); return } 491type KubeJobKanikoProps = { 493 dockerfileContent: string 494 tarSpecsA?: TarSpec[] 495 repo_name_tag: string 497 cluster_name?: string 498 dockreg_host?: string 504export const kubeJobKaniko = async ({name, dockerfileContent, tarSpecsA, repo_name_tag, dfLabel, cluster_name: cluster_nameArg, dockreg_host: dockreg_hostArg, dockLanHost: dockLanHostArg, action: actionArg, git_sha: git_shaArg}: KubeJobKanikoProps) => { 506 const cluster_name = cluster_nameArg || ctx.cluster_name, dockreg_host = dockreg_hostArg || ctx.dockreg_host, dockLanHost = dockLanHostArg || ctx.dockLanHost, action = actionArg || getAction() 507 const {git_sha: git_shaCfg, git_repo_name, gitsshHost: gitsshHostCfg, gitsshRepoPath: gitsshRepoPathCfg, gitsshHttpsHostname: gitsshHttpsHostnameCfg} = getAppCfg() 508 const kanikoLocalContext = getAppCfg()?.kanikoLocalContext ?? true 509 const git_sha = git_shaArg || git_shaCfg 510 const gitsshHost = (isOcWeb && process.env.GITSSH_HOST) || gitsshHostCfg || process.env.GITSSH_HOST 511 const gitsshRepoPath = (isOcWeb && process.env.GITSSH_REPO) || gitsshRepoPathCfg || process.env.GITSSH_REPO 512 const gitsshHttpsHostname = gitsshHttpsHostnameCfg 516 const useSsh = !!gitsshHost, useGitsshHttps = !!gitsshHttpsHostname 518 const isStandalone = !tarSpecsA?.length && !/^\s*(COPY|ADD)\b/im.test(dockerfileContent) 519 if (!isStandalone && !kanikoLocalContext) { 524 const kanikoStore = getKanikoCtx() 525 kanikoStore.cluster_name = cluster_name 526 kanikoStore.dockreg_host = dockreg_host 527 kanikoStore.git_sha = git_sha 528 const {appliedShared, gitChecked} = kanikoStore 529 const gitCheckKey = useGitsshHttps ? `https:${gitsshHttpsHostname}:${gitsshRepoPath}:${git_sha}` : useSsh ? `${gitsshHost}:${gitsshRepoPath}:${git_sha}` : `${git_repo_name}:${git_sha}` 530 if (!isStandalone && !kanikoLocalContext && !gitChecked.has(gitCheckKey)) { 531 if (useGitsshHttps) { 533 const repoName = gitsshRepoPath.replace(/^\/home\/git\/repos\//, '').replace(/\.git$/, '') 535 const tokenMissingHint = `run gitssh apply to generate ${gitsshHost}-httptoken` 537 const httpsUrl = `https://git:${httpsToken}@${gitsshHttpsHostname}/git/${repoName}.git` 538 logElapsed(`commitexists ${git_sha} (on ${gitsshHttpsHostname})`) 539 let {stdout, isSuccess} = await liveSpawn({cmd: `git ls-remote ${httpsUrl}`, isQuiet: true, noOutCmd: true, timeoutAfterSec: 15}) 540 if (!isSuccess || !String(stdout).includes(git_sha)) { 541 if (autoPushOnDeploy) { 543 await liveSpawn({cmd: `git push ${httpsUrl} HEAD:main`, noOutCmd: true, timeoutAfterSec: 60}) 544 ;({stdout, isSuccess} = await liveSpawn({cmd: `git ls-remote ${httpsUrl}`, isQuiet: true, noOutCmd: true, timeoutAfterSec: 15})) 546 const gitHttpsPushHint = 'git push to gitssh https' 547 assertTruthy(isSuccess && String(stdout).includes(git_sha), {git_sha, gitHttpsPushHint}) 550 const gitsshUrl = `ssh://git@${gitsshHost}${gitsshRepoPath}` 552 const repoName = gitsshRepoPath.replace(/^\/home\/git\/repos\//, '').replace(/\.git$/, '') 553 const checkCmd = `ssh git@${gitsshHost} commitexists ${repoName} ${git_sha}` 555 let {isSuccess} = await liveSpawn({cmd: checkCmd, isQuiet: true, noOutCmd: true, timeoutAfterSec: 10}) 557 if (autoPushOnDeploy) { 559 await liveSpawn({cmd: `git push ${gitsshUrl} HEAD:main`, timeoutAfterSec: 30}) 560 const {isSuccess: isSuccessRetry} = await liveSpawn({cmd: checkCmd, isQuiet: true, noOutCmd: true, timeoutAfterSec: 10}) 561 if (isSuccessRetry) isSuccess = true 563 const gitPushHint = 'git push to gitssh' 567 const result = await liveRepoExec({cmd: `git branch -a --contains ${git_sha}`, git_repo_name}) 568 const gitPushHint = 'git push so kaniko can pull' 569 assertTruthy(result.includes('remotes/origin'), {git_sha, hint: gitPushHint}) 571 gitChecked.add(gitCheckKey) 574 if (kanikoLocalContext && tarSpecsA?.length) getKanikoCtx().localCtxTarPath = await genImportTreeCtxTar({tarSpecsA}) 575 const kanikoProps = {name, dockerfileContent, tarSpecsA, df_sha, isStandalone, repo_name_tag, action, gitsshHost, gitsshRepoPath, gitsshHttpsHostname} 576 const {newJob, gitRemoteSecret, resources} = await getKanikoResources(kanikoProps) 578 const sharedKey = `${cluster_name}:${dockreg_host}` 579 const isSharedApplied = appliedShared.has(sharedKey) 580 const perBuildResources = resources.filter(r => r.kind === 'ConfigMap') 581 const sharedResources = resources.filter(r => r.kind !== 'ConfigMap') 582 if (action != 'delete') sharedResources.push(regcredSecTempl({dockreg_host, dockLanHost, cluster_name})) 584 await Promise.all(perBuildResources.map((resource) => resourcesActionCore({resources: [resource], action, cluster_name}))) 585 if (!isSharedApplied) { 586 await Promise.all(sharedResources.map((resource) => resourcesActionCore({resources: [resource], action, cluster_name}))) 587 appliedShared.add(sharedKey) 591 if (!useSsh && gitRemoteSecret && !isSharedApplied) await objectApiCall({resource: gitRemoteSecret, objectApiMethod: 'create', cluster_name}) 592 await kanikoRetryable({newJob, dfLabel, repo_name_tag})