Skip to content

Commit

Permalink
refactor: replace hasha.fromStream with new fn hashStream (#25046)
Browse files Browse the repository at this point in the history
  • Loading branch information
RahulGautamSingh committed Oct 20, 2023
1 parent b565ee3 commit 08c0969
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 34 deletions.
4 changes: 2 additions & 2 deletions lib/modules/datasource/github-release-attachments/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import is from '@sindresorhus/is';
import hasha from 'hasha';
import { logger } from '../../../logger';
import { cache } from '../../../util/cache/package/decorator';
import { queryReleases } from '../../../util/github/graphql';
Expand All @@ -9,6 +8,7 @@ import type {
GithubRestRelease,
} from '../../../util/github/types';
import { getApiBaseUrl, getSourceUrl } from '../../../util/github/url';
import { hashStream } from '../../../util/hash';
import { GithubHttp } from '../../../util/http/github';
import { newlineRegex, regEx } from '../../../util/regex';
import { Datasource } from '../datasource';
Expand Down Expand Up @@ -84,7 +84,7 @@ export class GithubReleaseAttachmentsDatasource extends Datasource {
algorithm: string
): Promise<string> {
const res = this.http.stream(asset.browser_download_url);
const digest = await hasha.fromStream(res, { algorithm });
const digest = await hashStream(res, algorithm);
return digest;
}

Expand Down
6 changes: 2 additions & 4 deletions lib/modules/manager/bazel/artifacts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import is from '@sindresorhus/is';
import hasha from 'hasha';
import { logger } from '../../../logger';
import * as packageCache from '../../../util/cache/package';
import { hashStream } from '../../../util/hash';
import { Http } from '../../../util/http';
import { map as pMap } from '../../../util/promises';
import { regEx } from '../../../util/regex';
Expand Down Expand Up @@ -76,9 +76,7 @@ async function getHashFromUrl(url: string): Promise<string | null> {
return cachedResult;
}
try {
const hash = await hasha.fromStream(http.stream(url), {
algorithm: 'sha256',
});
const hash = await hashStream(http.stream(url), 'sha256');
const cacheMinutes = 3 * 24 * 60; // 3 days
await packageCache.set(cacheNamespace, url, hash, cacheMinutes);
return hash;
Expand Down
10 changes: 3 additions & 7 deletions lib/modules/manager/homebrew/update.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// TODO: types (#22198)
import hasha from 'hasha';
import semver from 'semver';
import { logger } from '../../../logger';
import { hashStream } from '../../../util/hash';
import { Http } from '../../../util/http';
import type { UpdateDependencyConfig } from '../types';
import { parseUrlPath } from './extract';
Expand Down Expand Up @@ -163,9 +163,7 @@ export async function updateDependency({
newUrl = `https://github.com/${ownerName}/${repoName}/releases/download/${
upgrade.newValue
}/${repoName}-${String(semver.coerce(upgrade.newValue))}.tar.gz`;
newSha256 = await hasha.fromStream(http.stream(newUrl), {
algorithm: 'sha256',
});
newSha256 = await hashStream(http.stream(newUrl), 'sha256');
} catch (errOuter) {
logger.debug(
`Failed to download release download for ${upgrade.depName} - trying archive instead`
Expand All @@ -174,9 +172,7 @@ export async function updateDependency({
const ownerName = String(upgrade.managerData.ownerName);
const repoName = String(upgrade.managerData.repoName);
newUrl = `https://github.com/${ownerName}/${repoName}/archive/${upgrade.newValue}.tar.gz`;
newSha256 = await hasha.fromStream(http.stream(newUrl), {
algorithm: 'sha256',
});
newSha256 = await hashStream(http.stream(newUrl), 'sha256');
} catch (errInner) {
logger.debug(
`Failed to download archive download for ${upgrade.depName} - update failed`
Expand Down
41 changes: 38 additions & 3 deletions lib/util/hash.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { hash, toSha256 } from './hash';
import * as crypto from 'crypto';
import { Readable } from 'stream';
import { hash, hashStream, toSha256 } from './hash';

describe('util/hash', () => {
test('should hash data with sha256', () => {
it('hashes data with sha256', () => {
expect(hash('https://example.com/test.txt', 'sha256')).toBe(
'd1dc63218c42abba594fff6450457dc8c4bfdd7c22acf835a50ca0e5d2693020'
);
Expand All @@ -10,9 +12,42 @@ describe('util/hash', () => {
);
});

test('should hash data with sha512', () => {
it('hashes data with sha512', () => {
expect(hash('https://example.com/test.txt')).toBe(
'368b1e723aecb5d17e0a69d046f8a7b9eb4e2aa2ee78e307d563c57cde45b8c3755990411aa2626c13214a8d571e0478fa9a19d03e295bb28bc453a88206b484'
);
});

it('correctly hashes the content of a readable stream', async () => {
const content = 'This is some test content.';
const expectedHash = crypto
.createHash('sha256')
.update(content)
.digest('hex');

// Create a readable stream from the content
const readableStream = new Readable();
readableStream.push(content);
readableStream.push(null);

const actualHash = await hashStream(readableStream, 'sha256');

expect(actualHash).toBe(expectedHash);
});

it('uses sha512 if no algorithm is specified', async () => {
const content = 'This is some test content.';
const expectedHash = crypto
.createHash('sha512')
.update(content)
.digest('hex');
// Create a readable stream from the content
const readableStream = new Readable();
readableStream.push(content);
readableStream.push(null);

const actualHash = await hashStream(readableStream);

expect(actualHash).toBe(expectedHash);
});
});
23 changes: 16 additions & 7 deletions lib/util/hash.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import crypto from 'node:crypto';
import crypto from 'crypto';
import { pipeline } from 'stream/promises';
import type { LiteralUnion } from 'type-fest';

const defaultAlgorithm: AlgorithmName = 'sha512';

export type AlgorithmName = LiteralUnion<
'sha1' | 'sha224' | 'sha256' | 'sha384' | 'sha512',
string
>;

export function hash(data: string | Buffer, algorithm?: AlgorithmName): string {
const selectedAlgorithm = algorithm ?? defaultAlgorithm;

const hash = crypto.createHash(selectedAlgorithm);
export function hash(
data: string | Buffer,
algorithm: AlgorithmName = 'sha512'
): string {
const hash = crypto.createHash(algorithm);
hash.update(data);
return hash.digest('hex');
}

export function toSha256(input: string): string {
return hash(input, 'sha256');
}

export async function hashStream(
inputStream: NodeJS.ReadableStream,
algorithm: AlgorithmName = 'sha512'
): Promise<string> {
const hash = crypto.createHash(algorithm);
await pipeline(inputStream, hash);
return hash.digest('hex');
}
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@
"node": "^18.12.0 || >=20.0.0"
},
"dependencies": {
"@aws-sdk/credential-providers": "3.363.0",
"@aws-sdk/client-codecommit": "3.363.0",
"@aws-sdk/client-ec2": "3.363.0",
"@aws-sdk/client-ecr": "3.363.0",
"@aws-sdk/client-rds": "3.363.0",
"@aws-sdk/client-s3": "3.363.0",
"@aws-sdk/credential-providers": "3.363.0",
"@breejs/later": "4.1.0",
"@cdktf/hcl2json": "0.18.2",
"@iarna/toml": "3.0.0",
Expand Down Expand Up @@ -206,7 +206,6 @@
"got": "11.8.6",
"graph-data-structure": "3.3.0",
"handlebars": "4.7.8",
"hasha": "5.2.2",
"ignore": "5.2.4",
"ini": "4.1.1",
"js-yaml": "4.1.0",
Expand Down
5 changes: 2 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 3 additions & 6 deletions tools/generate-imports.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import fs from 'fs-extra';
import { glob } from 'glob';
import hasha from 'hasha';
import { minimatch } from 'minimatch';
import upath from 'upath';
import { hashFile, hashFromArray } from './utils/hash.mjs';

console.log('generating imports');
const newFiles = new Set();
Expand Down Expand Up @@ -34,9 +34,6 @@ const dataPaths = [
'data',
'node_modules/emojibase-data/en/shortcodes/github.json',
];
const options = {
algorithm: 'sha256',
};

/**
*
Expand Down Expand Up @@ -74,7 +71,7 @@ function expandPaths(paths) {
*/
async function getFileHash(filePath) {
try {
const hash = await hasha.fromFile(filePath, options);
const hash = await hashFile(filePath, 'sha256');
return hash;
} catch (err) {
throw new Error(`ERROR: Unable to generate hash for ${filePath}`);
Expand Down Expand Up @@ -108,7 +105,7 @@ export async function getManagerHash(managerName, isCustomManager) {
}

if (hashes.length) {
return hasha(hashes, options);
return hashFromArray(hashes, 'sha256');
}

throw new Error(`Unable to generate hash for manager/${managerName}`);
Expand Down
30 changes: 30 additions & 0 deletions tools/utils/hash.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import crypto from 'node:crypto';
import fs from 'node:fs/promises';

/**
* Generate hash from array of strings
* @param {string[]} input
* @param {string} algorithm
* @returns {string}
*/
export function hashFromArray(input, algorithm = 'sha512') {
const hash = crypto.createHash(algorithm);
for (const str of input) {
hash.update(str);
}

return hash.digest('hex');
}

/**
* Generate hash from array of strings
* @param {string} file
* @param {string} algorithm
* @returns {Promise<string>}
*/
export async function hashFile(file, algorithm = 'sha512') {
const data = await fs.readFile(file);
const hash = crypto.createHash(algorithm);
hash.update(data);
return hash.digest('hex');
}

0 comments on commit 08c0969

Please sign in to comment.