UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

143 lines 7.58 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TerraformProviderHash = void 0; const tslib_1 = require("tslib"); const node_crypto_1 = tslib_1.__importDefault(require("node:crypto")); const extract_zip_1 = tslib_1.__importDefault(require("extract-zip")); const upath_1 = tslib_1.__importDefault(require("upath")); const logger_1 = require("../../../../logger"); const array_1 = require("../../../../util/array"); const decorator_1 = require("../../../../util/cache/package/decorator"); const fs = tslib_1.__importStar(require("../../../../util/fs")); const fs_1 = require("../../../../util/fs"); const http_1 = require("../../../../util/http"); const p = tslib_1.__importStar(require("../../../../util/promises")); const terraform_provider_1 = require("../../../datasource/terraform-provider"); class TerraformProviderHash { static http = new http_1.Http(terraform_provider_1.TerraformProviderDatasource.id); static terraformDatasource = new terraform_provider_1.TerraformProviderDatasource(); static hashCacheTTL = 10080; // in minutes == 1 week static async hashElementList(basePath, fileSystemEntries) { const rootHash = node_crypto_1.default.createHash('sha256'); for (const entryPath of fileSystemEntries) { const absolutePath = upath_1.default.resolve(basePath, entryPath); if (!(await fs.cachePathIsFile(absolutePath))) { continue; } // build for every file a line looking like "aaaaaaaaaaaaaaa file.txt\n" // get hash of specific file const hash = node_crypto_1.default.createHash('sha256'); const fileBuffer = await fs.readCacheFile(absolutePath); hash.update(fileBuffer); const line = `${hash.digest('hex')} ${upath_1.default.normalize(entryPath)}\n`; rootHash.update(line); } return rootHash.digest('base64'); } /** * This is a reimplementation of the Go H1 hash algorithm found at https://github.com/golang/mod/blob/master/sumdb/dirhash/hash.go * The package provides two function HashDir and HashZip where the first is for hashing the contents of a directory * and the second for doing the same but implicitly extracting the contents first. * * The problem starts with that there is a bug which leads to the fact that HashDir and HashZip do not return the same * hash if there are folders inside the content which should be hashed. * * In a folder structure such as * . * ├── Readme.md * └── readme-assets/ * └── image.jpg * * HashDir will create a list of following entries which in turn will hash again * aaaaaaaaaaa Readme.md\n * ccccccccccc readme-assets/image.jpg\n * * HashZip in contrast will not filter out the directory itself but rather includes it in the hash list * aaaaaaaaaaa Readme.md\n * bbbbbbbbbbb readme-assets/\n * ccccccccccc readme-assets/image.jpg\n * * As the resulting string is used to generate the final hash it will differ based on which function has been used. * The issue is tracked here: https://github.com/golang/go/issues/53448 * * This implementation follows the intended implementation and filters out folder entries. * Terraform seems NOT to use HashZip for provider validation, but rather extracts it and then do the hash calculation * even as both are set up in their code base. * https://github.com/hashicorp/terraform/blob/3fdfbd69448b14a4982b3c62a5d36835956fcbaa/internal/getproviders/hash.go#L283-L305 * * @param zipFilePath path to the zip file * @param extractPath path to where to temporarily extract the data */ static async hashOfZipContent(zipFilePath, extractPath) { await (0, extract_zip_1.default)(zipFilePath, { dir: extractPath, }); const hash = await this.hashOfDir(extractPath); // delete extracted files await fs.rmCache(extractPath); return hash; } static async hashOfDir(dirPath) { const elements = await fs.listCacheDir(dirPath, { recursive: true }); const sortedFileSystemObjects = elements.sort(); return await TerraformProviderHash.hashElementList(dirPath, sortedFileSystemObjects); } static async calculateSingleHash(build, cacheDir) { const downloadFileName = upath_1.default.join(cacheDir, build.filename); const extractPath = upath_1.default.join(cacheDir, 'extract', build.filename); logger_1.logger.trace(`Downloading archive and generating hash for ${build.name}-${build.version}...`); const startTime = Date.now(); const readStream = TerraformProviderHash.http.stream(build.url); const writeStream = fs.createCacheWriteStream(downloadFileName); try { await fs.pipeline(readStream, writeStream); const hash = await this.hashOfZipContent(downloadFileName, extractPath); logger_1.logger.debug(`Hash generation for ${build.url} took ${Date.now() - startTime}ms for ${build.name}-${build.version}`); return hash; } finally { // delete zip file await fs.rmCache(downloadFileName); } } static async calculateHashScheme1Hashes(builds) { logger_1.logger.debug(`Calculating hashes for ${builds.length} builds`); const cacheDir = await (0, fs_1.ensureCacheDir)('./others/terraform'); // for each build download ZIP, extract content and generate hash for all containing files return p.map(builds, (build) => this.calculateSingleHash(build, cacheDir), { concurrency: 4, }); } static async createHashes(registryURL, repository, version) { logger_1.logger.debug(`Creating hashes for ${repository}@${version} (${registryURL})`); const builds = await TerraformProviderHash.terraformDatasource.getBuilds(registryURL, repository, version); if (!builds) { return null; } // check if the publisher uses one shasum file for all builds or separate ones // we deduplicate to reduce the number of API calls const shaUrls = (0, array_1.deduplicateArray)(builds.map((build) => build.shasums_url).filter(array_1.isNotNullOrUndefined)); logger_1.logger.debug(`Getting zip hashes for ${shaUrls.length} shasum URL(s) for ${repository}@${version}`); const zhHashes = []; for (const shaUrl of shaUrls) { const hashes = await TerraformProviderHash.terraformDatasource.getZipHashes(shaUrl); zhHashes.push(...(0, array_1.coerceArray)(hashes)); } logger_1.logger.debug(`Got ${zhHashes.length} zip hashes for ${repository}@${version}`); const h1Hashes = await TerraformProviderHash.calculateHashScheme1Hashes(builds); const hashes = []; hashes.push(...h1Hashes.map((hash) => `h1:${hash}`)); hashes.push(...zhHashes.map((hash) => `zh:${hash}`)); // sorting the hash alphabetically as terraform does this as well return hashes.sort(); } } exports.TerraformProviderHash = TerraformProviderHash; tslib_1.__decorate([ (0, decorator_1.cache)({ namespace: `terraform-provider-hash`, key: (build) => `calculateSingleHash:${build.url}`, ttlMinutes: TerraformProviderHash.hashCacheTTL, }) ], TerraformProviderHash, "calculateSingleHash", null); //# sourceMappingURL=hash.js.map