k8s-deploy/src/strategyHelpers/canary/podCanaryHelper.ts
David Gamero 01cfe404ef
Migrate to esbuild/Vitest and upgrade @actions/* to ESM-only versions (#492)
* Migrate build toolchain from ncc/Jest to esbuild/Vitest

Replace the legacy ncc/Jest/Babel build stack with a modern ESM toolchain:

Build:
- Replace @vercel/ncc with esbuild (--platform=node --target=node20 --format=esm)
- Add createRequire banner for CJS interop in ESM bundle
- Add "type": "module" to package.json
- Add tsc --noEmit typecheck script (esbuild strips types without checking)
- Add typecheck to husky pre-commit hook

Dependencies:
- Bump @actions/core@3, exec@3, io@3, tool-cache@4 (ESM-only)
- Replace jest/ts-jest/@babel/* with vitest@4

Tests:
- Convert 29 test files: jest.fn()→vi.fn(), jest.mock()→vi.mock(), jest.spyOn()→vi.spyOn()
- Fix vitest 4 compat: mockImplementation requires args, mock call tracking, await .rejects

CI:
- Update build step from ncc build → npm run build
- Update composite action to use npm run build

* Switch tsconfig to NodeNext module resolution

Change module/moduleResolution from ES2022/bundler to NodeNext/NodeNext
and target from ES2022 to ES2020.

- Add .js extensions to all relative imports across 59 source/test files
  (required by NodeNext module resolution)
- Add vitest/globals to tsconfig types array for global test API declarations
2026-02-24 11:57:56 -08:00

119 lines
4.3 KiB
TypeScript

import {Kubectl} from '../../types/kubectl.js'
import * as core from '@actions/core'
import * as fs from 'fs'
import * as yaml from 'js-yaml'
import * as fileHelper from '../../utilities/fileUtils.js'
import * as canaryDeploymentHelper from './canaryHelper.js'
import {isDeploymentEntity} from '../../types/kubernetesTypes.js'
import {getReplicaCount} from '../../utilities/manifestUpdateUtils.js'
import {DeployResult} from '../../types/deployResult.js'
import {K8sObject} from '../../types/k8sObject.js'
import {checkForErrors} from '../../utilities/kubectlUtils.js'
export async function deployPodCanary(
filePaths: string[],
kubectl: Kubectl,
onlyDeployStable: boolean = false,
timeout?: string
): Promise<DeployResult> {
const newObjectsList = []
const percentage = parseInt(core.getInput('percentage', {required: true}))
if (percentage < 0 || percentage > 100)
throw Error('Percentage must be between 0 and 100')
for (const filePath of filePaths) {
try {
const fileContents = fs.readFileSync(filePath, 'utf8')
const parsedYaml = yaml.loadAll(fileContents)
for (const inputObject of parsedYaml) {
if (
inputObject &&
typeof inputObject === 'object' &&
'metadata' in inputObject &&
'kind' in inputObject &&
'spec' in inputObject &&
typeof inputObject.metadata === 'object' &&
'name' in inputObject.metadata &&
typeof inputObject.metadata.name === 'string' &&
typeof inputObject.kind === 'string'
) {
const obj = inputObject as K8sObject
const name = obj.metadata.name
const kind = obj.kind
if (!onlyDeployStable && isDeploymentEntity(kind)) {
core.debug('Calculating replica count for canary')
const canaryReplicaCount = calculateReplicaCountForCanary(
obj,
percentage
)
core.debug('Replica count is ' + canaryReplicaCount)
const newCanaryObject =
canaryDeploymentHelper.getNewCanaryResource(
obj,
canaryReplicaCount
)
newObjectsList.push(newCanaryObject)
// if there's already a stable object, deploy baseline as well
const stableObject =
await canaryDeploymentHelper.fetchResource(
kubectl,
kind,
name
)
if (stableObject) {
core.debug(
`Stable object found for ${kind} ${name}. Creating baseline objects`
)
const newBaselineObject =
canaryDeploymentHelper.getNewBaselineResource(
stableObject,
canaryReplicaCount
)
core.debug(
'New baseline object: ' +
JSON.stringify(newBaselineObject)
)
newObjectsList.push(newBaselineObject)
}
} else {
// deploy non deployment entity or regular deployments for promote as they are
newObjectsList.push(obj)
}
}
}
} catch (error) {
core.error(
`Failed to parse YAML file at ${filePath}: ${error.message}`
)
throw error
}
}
core.debug('New objects list: ' + JSON.stringify(newObjectsList))
const manifestFiles = fileHelper.writeObjectsToFile(newObjectsList)
const forceDeployment = core.getInput('force').toLowerCase() === 'true'
const serverSideApply = core.getInput('server-side').toLowerCase() === 'true'
const execResult = await kubectl.apply(
manifestFiles,
forceDeployment,
serverSideApply,
timeout
)
checkForErrors([execResult])
return {execResult, manifestFiles}
}
export function calculateReplicaCountForCanary(
inputObject: any,
percentage: number
) {
const inputReplicaCount = getReplicaCount(inputObject)
return Math.max(1, Math.round((inputReplicaCount * percentage) / 100))
}