import crypto from 'crypto' ; import extract from 'extract-zip' ; import pMap from 'p-map' ; import upath from 'upath' ; import { TerraformProviderDatasource } from '../../../datasource/terraform-provider' ; import type { TerraformBuild } from '../../../datasource/terraform-provider/types' ; import { logger } from '../../../logger' ; import { cache } from '../../../util/cache/package/decorator' ; import * as fs from '../../../util/fs' ; import { ensureCacheDir } from '../../../util/fs' ; import { Http } from '../../../util/http' ; import { regEx } from '../../../util/regex' ; export class TerraformProviderHash { static http = new Http ( TerraformProviderDatasource . id ) ; static terraformDatasource = new TerraformProviderDatasource ( ) ; static hashCacheTTL = 10080 ; private static async hashFiles ( files ) { const rootHash = crypto . createHash ( 'sha256' ) ; for ( const file of files ) { const hash = crypto . createHash ( 'sha256' ) ; const fileBuffer = await fs . readFile ( file ) ; hash . update ( fileBuffer ) ; rootHash . update ( hash . digest ( 'hex' ) ) ; rootHash . update ( '  ' ) ; const fileName = file . replace ( regEx ( /^.*[\\/]/ ) , '' ) ; rootHash . update ( fileName ) ; rootHash . update ( '\n' ) ; } return rootHash . digest ( 'base64' ) ; } static async hashOfZipContent ( zipFilePath , extractPath ) { await extract ( zipFilePath , { dir : extractPath } ) ; const files = await fs . readdir ( extractPath ) ; const sortedFiles = files . sort ( ( a , b ) => a . localeCompare ( b ) ) ; const filesWithPath = sortedFiles . map ( ( file ) => `${ extractPath }/${ file }` ) ; const result = await TerraformProviderHash . hashFiles ( filesWithPath ) ; await fs . rm ( extractPath , { recursive : true } ) ; return result ; } @ cache ( { namespace : `datasource-${ TerraformProviderDatasource . id }-build-hashes` , key : ( build ) => build . url , ttlMinutes : TerraformProviderHash . hashCacheTTL , } ) static async calculateSingleHash ( build , cacheDir ) { const downloadFileName = upath . join ( cacheDir , build . filename ) ; const extractPath = upath . join ( cacheDir , 'extract' , build . filename ) ; logger . trace ( `Downloading archive and generating hash for ${ build . name }-${ build . version }...` ) ; const readStream = TerraformProviderHash . http . stream ( build . url ) ; const writeStream = fs . createWriteStream ( downloadFileName ) ; try { await fs . pipeline ( readStream , writeStream ) ; const hash = await this . hashOfZipContent ( downloadFileName , extractPath ) ; logger . trace ( { hash } , `Generated hash for ${ build . name }-${ build . version }` ) ; return hash ; } finally { await fs . unlink ( downloadFileName ) ; } } static async calculateHashes ( builds ) { const cacheDir = await ensureCacheDir ( './others/terraform' ) ; return pMap ( builds , ( build ) => this . calculateSingleHash ( build , cacheDir ) , { concurrency : 4 } ) ; } static async createHashes ( registryURL , repository , version ) { const builds = await TerraformProviderHash . terraformDatasource . getBuilds ( registryURL , repository , version ) ; if ( ! builds ) { return null ; } const hashes = await TerraformProviderHash . calculateHashes ( builds ) ; return hashes . sort ( ) . map ( ( hash ) => `h1:${ hash }` ) ; } }