Sort 'find' output before hashing for consistency (#267)

* Sort 'find' output before hashing for consistency across different platforms

* fix style issues
This commit is contained in:
zarko-a
2025-11-04 05:06:36 -06:00
committed by GitHub
parent ea25fd1b3e
commit 2934de33f8

View File

@@ -271,19 +271,18 @@ export async function execPodStep(
}) })
} }
export async function execCalculateOutputHash( export async function execCalculateOutputHashSorted(
podName: string, podName: string,
containerName: string, containerName: string,
command: string[] command: string[]
): Promise<string> { ): Promise<string> {
const exec = new k8s.Exec(kc) const exec = new k8s.Exec(kc)
// Create a writable stream that updates a SHA-256 hash with stdout data let output = ''
const hash = createHash('sha256') const outputWriter = new stream.Writable({
const hashWriter = new stream.Writable({
write(chunk, _enc, cb) { write(chunk, _enc, cb) {
try { try {
hash.update(chunk.toString('utf8') as Buffer) output += chunk.toString('utf8')
cb() cb()
} catch (e) { } catch (e) {
cb(e as Error) cb(e as Error)
@@ -298,7 +297,7 @@ export async function execCalculateOutputHash(
podName, podName,
containerName, containerName,
command, command,
hashWriter, // capture stdout for hashing outputWriter, // capture stdout
process.stderr, process.stderr,
null, null,
false /* tty */, false /* tty */,
@@ -320,27 +319,46 @@ export async function execCalculateOutputHash(
.catch(e => reject(e)) .catch(e => reject(e))
}) })
// finalize hash and return digest outputWriter.end()
hashWriter.end()
// Sort lines for consistent ordering across platforms
const sortedOutput =
output
.split('\n')
.filter(line => line.length > 0)
.sort()
.join('\n') + '\n'
const hash = createHash('sha256')
hash.update(sortedOutput)
return hash.digest('hex') return hash.digest('hex')
} }
export async function localCalculateOutputHash( export async function localCalculateOutputHashSorted(
commands: string[] commands: string[]
): Promise<string> { ): Promise<string> {
return await new Promise<string>((resolve, reject) => { return await new Promise<string>((resolve, reject) => {
const hash = createHash('sha256')
const child = spawn(commands[0], commands.slice(1), { const child = spawn(commands[0], commands.slice(1), {
stdio: ['ignore', 'pipe', 'ignore'] stdio: ['ignore', 'pipe', 'ignore']
}) })
let output = ''
child.stdout.on('data', chunk => { child.stdout.on('data', chunk => {
hash.update(chunk) output += chunk.toString('utf8')
}) })
child.on('error', reject) child.on('error', reject)
child.on('close', (code: number) => { child.on('close', (code: number) => {
if (code === 0) { if (code === 0) {
// Sort lines for consistent ordering across distributions/platforms
const sortedOutput =
output
.split('\n')
.filter(line => line.length > 0)
.sort()
.join('\n') + '\n'
const hash = createHash('sha256')
hash.update(sortedOutput)
resolve(hash.digest('hex')) resolve(hash.digest('hex'))
} else { } else {
reject(new Error(`child process exited with code ${code}`)) reject(new Error(`child process exited with code ${code}`))
@@ -400,7 +418,7 @@ export async function execCpToPod(
} }
} }
const want = await localCalculateOutputHash([ const want = await localCalculateOutputHashSorted([
'sh', 'sh',
'-c', '-c',
listDirAllCommand(runnerPath) listDirAllCommand(runnerPath)
@@ -410,11 +428,11 @@ export async function execCpToPod(
const delay = 1000 const delay = 1000
for (let i = 0; i < attempts; i++) { for (let i = 0; i < attempts; i++) {
try { try {
const got = await execCalculateOutputHash(podName, JOB_CONTAINER_NAME, [ const got = await execCalculateOutputHashSorted(
'sh', podName,
'-c', JOB_CONTAINER_NAME,
listDirAllCommand(containerPath) ['sh', '-c', listDirAllCommand(containerPath)]
]) )
if (got !== want) { if (got !== want) {
core.debug( core.debug(
@@ -441,11 +459,11 @@ export async function execCpFromPod(
core.debug( core.debug(
`Copying from pod ${podName} ${containerPath} to ${targetRunnerPath}` `Copying from pod ${podName} ${containerPath} to ${targetRunnerPath}`
) )
const want = await execCalculateOutputHash(podName, JOB_CONTAINER_NAME, [ const want = await execCalculateOutputHashSorted(
'sh', podName,
'-c', JOB_CONTAINER_NAME,
listDirAllCommand(containerPath) ['sh', '-c', listDirAllCommand(containerPath)]
]) )
let attempt = 0 let attempt = 0
while (true) { while (true) {
@@ -506,7 +524,7 @@ export async function execCpFromPod(
const delay = 1000 const delay = 1000
for (let i = 0; i < attempts; i++) { for (let i = 0; i < attempts; i++) {
try { try {
const got = await localCalculateOutputHash([ const got = await localCalculateOutputHashSorted([
'sh', 'sh',
'-c', '-c',
listDirAllCommand(targetRunnerPath) listDirAllCommand(targetRunnerPath)