🌳
pt0/deployF/testsF/dualDeployActionAI.mts
2import { runTestsCore, defaultRunTestsOpts, type RunTestsCoreConfig } from './runTestsCoreAI.mts'
4import { klusterCtx, type KlusterCfg } from '../k8sF/ctxF/klusterCtxF.mts'
20export type DualDeployConfig = {
21 getTestConfig: () => Promise<RunTestsCoreConfig>
22 smokeOverlay: object
23 prodOverlay: object
24 smokeSuites: string[]
25 prodVerifySuites: string[]
28const getEpPath = () => toPtRelPath(getAppCfg()?.importMetaUrl || '')
30const applyOverlay = async (overlay: object) => {
31 const appCfg = getAppCfg()
32 mutateAppCfg(appCfg, overlay, {action: 'apply' as const})
33 mutateAppCfg(appCfg, {kube_extHostname: getAnyHostname(appCfg as any)})
34 const deployedHn = getAnyHostname(appCfg as any)
35 if (deployedHn) mutateAppCfg(appCfg, {deployedBaseUrl: `https://${deployedHn}`})
36 const dbSecrets = (appCfg as any).envConf?.dbSecrets || {}
37 const dbSecretsMapping = dbSecretsToSecretsMapping(dbSecrets)
38 ;(appCfg as any).envConf.secretsMapping = { ...(appCfg as any).envConf.secretsMapping, ...dbSecretsMapping }
39 mutateAppCfg(appCfg, {secretNames: secretsMapToA((appCfg as any).envConf.secretsMapping)})
40 doSetProcessEnv({envLocal: (appCfg as any).envConf, envConf: (appCfg as any).envConf})
41 actionCtx.enterWith({action: 'apply'}) // ctx:clear
42 klusterCtx.enterWith(appCfg as KlusterCfg) // ctx:clear
43 cliValidatedCtx.enterWith({validated: true}) // ctx:clear
44 await applyAndWaitRollout(appCfg)
48export const dualDeployDescript = 'smoke apply → smoke testprod → prod apply → prod verify'
50export const mkDualDeploy = (cfg: DualDeployConfig) => {
51 const testdeploy = async () => {
52 const cli = parseCli(testdeployCli)
53 if (cli.history) {
54 printTestHistory({ ep: getAppCfg()?.importMetaUrl, actions: ['runtests', 'apply'] })
55 return
56 }
58 printTestdeployEta(getAppCfg()?.importMetaUrl || '')
60 const config = await cfg.getTestConfig()
61 const epPath = getEpPath()
62 const totalStart = Date.now()
63 const phases: Record<string, number> = {}
65 console.log(chalkCyan('smoke apply'))
66 const t2 = Date.now()
67 await applyOverlay(cfg.smokeOverlay)
68 phases.smoke_apply = elapsedSec(t2)
70 if (config.preDeployedTests) await config.preDeployedTests()
72 const t3 = Date.now()
73 const smokeOpts = { ...defaultRunTestsOpts(cfg.smokeSuites, 'testprod'), isTestdeploy: true }
74 const smokeResult = await runTestsCore({ config, opts: smokeOpts })
75 phases.smoke_testprod = elapsedSec(t3)
76 if (!smokeResult.allPassed) {
77 console.log(chalkRed('smoke testprod failed, aborting before prod deploy'))
78 process.exit(1)
79 }
81 console.log(chalkCyan('prod apply'))
82 const t4 = Date.now()
83 await applyOverlay(cfg.prodOverlay)
84 phases.prod_apply = elapsedSec(t4)
86 const t5 = Date.now()
87 const prodVerifyOpts = { ...defaultRunTestsOpts(cfg.prodVerifySuites, 'testprod'), isTestdeploy: true }
88 const prodVerifyResult = await runTestsCore({ config, opts: prodVerifyOpts })
89 phases.prod_verify = elapsedSec(t5)
90 if (!prodVerifyResult.allPassed) {
91 console.log(chalkRed('prod verify failed'))
92 process.exit(1)
93 }
95 const testsActuallyRan = smokeResult.totalTests > 0 || prodVerifyResult.totalTests > 0
96 if (testsActuallyRan) {
97 const builder = currentBuilder()
98 const {imageReused} = getAppCfg() as {imageReused?: boolean}
100 ep: epPath,
101 action: 'testdeploy',
102 ts: new Date().toISOString(),
103 durationSec: elapsedSec(totalStart),
104 success: true,
105 gitSha: getGitShaFull(),
106 phases,
107 ptenv: 'testprod',
108 suites: { ...smokeResult.ranSuites, ...prodVerifyResult.ranSuites },
109 ...(builder !== 'docker' && {builder}),
110 ...(imageReused && {imageReused}),
111 })
112 }
114 console.log(chalkGreen('testdeploy complete'))
115 }
117 testdeploy.cliDescript = dualDeployDescript
118 testdeploy.cliSchema = testdeployCli
119 testdeploy.needsLazyDocker = true
120 return testdeploy