1import * as _ from 'lodash-es'2import { isActionSupported } from '../../deployF/k8sF/isActionSupportedF.mts'3import { nextVolMntsCtx } from './nextVolMntsCtxF.mts'4import { nextContCtx, ptKubeCtx } from '../../deployF/libF/nextContCtxF.mts'5import { imageFromAppCfg } from '../../deployF/dockerF/imageFromDfA.mts'6import { waitForDeploymentRollout } from '../../deployF/k8sF/waitForDeployRolloutAI.mts'7import { getImagePullSecrets } from '../../deployF/dockerF/dockRegF/getImagePullSecretsF.mts'8import { getVolumesForSecrets } from '../../deployF/getVolumesForSecretsF.mts'9import { mergeVolsMnts } from '../../deployF/k8sF/mergeVolsMntsF.mts'10import { generic3DeploymentTmpl } from '../../deployF/k8sF/genericDeploymentF.mts'11import { resourcesAction } from '../../deployF/k8sF/resourcesActionF.mts'12import { getAppCfg, type AppCfg } from '../../deployF/k8sF/ctxF/appCfgCtxF.mts'13import { recordApplyTiming, printActionEta } from '../../deployF/testsF/actionResultsAI.mts'14import { initCliTimer } from '../../deployF/cliF/cliTimerCtxF.mts'15import type { KubeResource } from '../../deployF/k8sF/ctxF/klusterCtxF.mts'16import { assertDefined } from '../../sharedF/assertsF/assertDefinedF.mts'17import { throwIf } from '../../sharedF/throwErrorF/throwIfF.mts'19const pvcPodAffinity = (colocateWith: string) => ({20 podAffinity: {21 requiredDuringSchedulingIgnoredDuringExecution: [{22 topologyKey: 'kubernetes.io/hostname',23 labelSelector: {24 matchExpressions: [{ key: 'name', operator: 'In', values: [colocateWith] }]25 }26 }]27 }28})30export type DeployAppCfg = AppCfg & { envLocal?: Record<string, string>; webReplicas?: number; deployStrategy?: {type: string}; monorDataPvcH?: unknown; colocateWithPod?: string; rolloutTimeoutMs?: number }32const mkNextDeployment = async (appConfig: DeployAppCfg) => {33 const { name, cluster_name, secretNames=[], envLocal, git_sha, buildSha, webReplicas: replicas=1, action='apply',34 deployStrategy, monorDataPvcH, colocateWithPod, rolloutTimeoutMs,35 } = appConfig36 assertDefined(name, {appConfig})37 assertDefined(cluster_name, {appConfig})39 const hasPvc = monorDataPvcH && cluster_name != 'minik'40 let strategy = deployStrategy as any41 let affinity: object | undefined42 if (hasPvc) {43 if (strategy && strategy.type !== 'Recreate') {44 throwIf(() => true, {name, deployStrategy, monorDataPvcH, msg: 'pvc-backed app must use Recreate strategy'})45 }46 strategy ??= {type: 'Recreate'}47 affinity = pvcPodAffinity(colocateWithPod || name)48 }50 const env = _.map(envLocal, (value, name) => ({name, value}))52 const deploymentBase = generic3DeploymentTmpl({name, strategy})54 const {image} = await imageFromAppCfg(appConfig)56 const envConf = appConfig.envConf as Record<string, string>57 const {promises, envConfHash, ...volMnts0} = getVolumesForSecrets({58 secretNames, envConf, cluster_name, name, action,59 })60 await Promise.all(promises)61 const volMntsA = [volMnts0]62 const volStore = nextVolMntsCtx.getStore()63 if (volStore) {64 const {resources: volResources, ...volMnts1} = volStore65 volMntsA.push(volMnts1)66 promises.push(resourcesAction({action, resources: volResources}))67 }68 const {volumes, volumeMounts} = mergeVolsMnts(...volMntsA)70 const resources: KubeResource[] = []71 const initContainers: object[] = []72 const extraContainers: object[] = []73 const annotations: Record<string, string> = {}75 const store = ptKubeCtx.getStore()76 if (store?.modVolsFnc) {77 await (store.modVolsFnc as (args: object) => Promise<void>)({volumes, volumeMounts, initContainers, resources, extraContainers, annotations, image})78 }80 const newDeployment = _.merge(deploymentBase, {81 metadata: {82 labels: {git_sha}83 },84 spec: {85 replicas,86 template: {87 metadata: {88 labels: {89 git_sha,90 },91 annotations: {92 envConfHash,93 ...(buildSha && buildSha !== git_sha && {build_sha: buildSha}),94 ...annotations,95 },96 },97 spec: {98 affinity,99 initContainers,100 containers: [101 {102 name: 'ptcont',103 image,104 env,105 volumeMounts,106 ...nextContCtx.getStore(),107 },108 ...extraContainers,109 ],110 volumes,111 imagePullSecrets: await getImagePullSecrets(),112 }113 }114 }115 })117 resources.push(newDeployment)118 await resourcesAction({resources, action, cluster_name})120 return newDeployment121}123export const applyAndWaitRollout = async (appConfig: DeployAppCfg) => {124 const {cluster_name, action='apply', rolloutTimeoutMs} = appConfig125 if (isActionSupported(action)) assertDefined(cluster_name, {appConfig})126 if (action === 'apply') initCliTimer()127 if (action === 'apply') printActionEta(getAppCfg()?.importMetaUrl || '', 'apply')128 const startTime = Date.now()130 let resource: KubeResource | undefined131 if (isActionSupported(action)) {132 resource = await mkNextDeployment(appConfig)133 }135 if (action === 'apply' && resource) {136 await waitForDeploymentRollout({resource, cluster_name: cluster_name!, rolloutTimeoutMs})138 recordApplyTiming({ep: appCfg?.importMetaUrl || '', startTime, gitSha: appCfg?.git_sha || '', buildSha: appCfg?.buildSha, imageReused: appCfg?.imageReused})139 }140}