import is from '@sindresorhus/is' ; import upath from 'upath' ; import { GlobalConfig } from '../../config/global' ; import { TEMPORARY_ERROR } from '../../constants/error-messages' ; import { logger } from '../../logger' ; import { generateInstallCommands , isDynamicInstall } from './buildpack' ; import { rawExec } from './common' ; import { generateDockerCommand , removeDockerContainer } from './docker' ; import { getChildProcessEnv } from './env' ; import type { DockerOptions , ExecOptions , ExecResult , ExtraEnv , Opt , RawExecOptions , } from './types' ; function getChildEnv ( { extraEnv , env : forcedEnv = { } , } ) { const globalConfigEnv = GlobalConfig . get ( 'customEnvVariables' ) ; const inheritedKeys = [ ] ; for ( const [ key , val ] of Object . entries ( extraEnv ?? { } ) ) { if ( is . string ( val ) ) { inheritedKeys . push ( key ) ; } } const parentEnv = getChildProcessEnv ( inheritedKeys ) ; const combinedEnv = { ... extraEnv , ... parentEnv , ... globalConfigEnv , ... forcedEnv , } ; const result = { } ; for ( const [ key , val ] of Object . entries ( combinedEnv ) ) { if ( is . string ( val ) ) { result [ key ] = `${ val }` ; } } return result ; } function dockerEnvVars ( extraEnv , childEnv ) { const extraEnvKeys = Object . keys ( extraEnv || { } ) ; return extraEnvKeys . filter ( ( key ) => is . nonEmptyString ( childEnv [ key ] ) ) ; } function getCwd ( { cwd , cwdFile } ) { const defaultCwd = GlobalConfig . get ( 'localDir' ) ; const paramCwd = cwdFile ? upath . join ( defaultCwd , upath . dirname ( cwdFile ) ) : cwd ; return paramCwd ?? defaultCwd ; } function getRawExecOptions ( opts ) { const defaultExecutionTimeout = GlobalConfig . get ( 'executionTimeout' ) ; const childEnv = getChildEnv ( opts ) ; const cwd = getCwd ( opts ) ; const rawExecOptions = { cwd , encoding : 'utf-8' , env : childEnv , maxBuffer : opts . maxBuffer , timeout : opts . timeout , } ; if ( ! rawExecOptions . timeout ) { if ( defaultExecutionTimeout ) { rawExecOptions . timeout = defaultExecutionTimeout * 60 * 1000 ; } else { rawExecOptions . timeout = 15 * 60 * 1000 ; } } rawExecOptions . maxBuffer = rawExecOptions . maxBuffer ?? 10 * 1024 * 1024 ; return rawExecOptions ; } function isDocker ( docker ) { const { binarySource } = GlobalConfig . get ( ) ; return binarySource === 'docker' && ! ! docker ; } interface RawExecArguments { rawCommands : string [ ] ; rawOptions : RawExecOptions ; } async function prepareRawExec ( cmd , opts = { } ) { const { docker } = opts ; const { customEnvVariables } = GlobalConfig . get ( ) ; const rawOptions = getRawExecOptions ( opts ) ; let rawCommands = typeof cmd === 'string' ? [ cmd ] : cmd ; if ( isDocker ( docker ) ) { logger . debug ( { image : docker . image } , 'Using docker to execute' ) ; const extraEnv = { ... opts . extraEnv , ... customEnvVariables } ; const childEnv = getChildEnv ( opts ) ; const envVars = dockerEnvVars ( extraEnv , childEnv ) ; const cwd = getCwd ( opts ) ; const dockerOptions = { ... docker , cwd , envVars } ; const preCommands = [ ... ( await generateInstallCommands ( opts . toolConstraints ) ) , ... ( opts . preCommands ?? [ ] ) , ] ; const dockerCommand = await generateDockerCommand ( rawCommands , preCommands , dockerOptions ) ; rawCommands = [ dockerCommand ] ; } else if ( isDynamicInstall ( opts . toolConstraints ) ) { logger . debug ( 'Using buildpack dynamic installs' ) ; rawCommands = [ ... ( await generateInstallCommands ( opts . toolConstraints ) ) , ... rawCommands , ] ; } return { rawCommands , rawOptions } ; } export async function exec ( cmd , opts = { } ) { const { docker } = opts ; const dockerChildPrefix = GlobalConfig . get ( 'dockerChildPrefix' ) ; const { rawCommands , rawOptions } = await prepareRawExec ( cmd , opts ) ; const useDocker = isDocker ( docker ) ; let res = { stdout : '' , stderr : '' } ; for ( const rawCmd of rawCommands ) { const startTime = Date . now ( ) ; if ( useDocker ) { await removeDockerContainer ( docker . image , dockerChildPrefix ) ; } logger . debug ( { command : rawCmd } , 'Executing command' ) ; logger . trace ( { commandOptions : rawOptions } , 'Command options' ) ; try { res = await rawExec ( rawCmd , rawOptions ) ; } catch ( err ) { logger . debug ( { err } , 'rawExec err' ) ; if ( useDocker ) { await removeDockerContainer ( docker . image , dockerChildPrefix ) . catch ( ( removeErr ) => { const message = err . message ; throw new Error ( `Error: "${ removeErr . message }" - Original Error: "${ message }"` ) ; } ) ; } if ( err . signal === `SIGTERM` ) { logger . debug ( { err } , 'exec interrupted by SIGTERM - run needs to be aborted' ) ; throw new Error ( TEMPORARY_ERROR ) ; } throw err ; } const durationMs = Math . round ( Date . now ( ) - startTime ) ; logger . debug ( { cmd : rawCmd , durationMs , stdout : res . stdout , stderr : res . stderr , } , 'exec completed' ) ; } return res ; }