2022-11-22 16:47:27 +00:00
"use strict" ;
var _ _createBinding = ( this && this . _ _createBinding ) || ( Object . create ? ( function ( o , m , k , k2 ) {
if ( k2 === undefined ) k2 = k ;
2023-01-18 20:00:33 +00:00
var desc = Object . getOwnPropertyDescriptor ( m , k ) ;
if ( ! desc || ( "get" in desc ? ! m . _ _esModule : desc . writable || desc . configurable ) ) {
desc = { enumerable : true , get : function ( ) { return m [ k ] ; } } ;
}
Object . defineProperty ( o , k2 , desc ) ;
2022-11-22 16:47:27 +00:00
} ) : ( function ( o , m , k , k2 ) {
if ( k2 === undefined ) k2 = k ;
o [ k2 ] = m [ k ] ;
} ) ) ;
var _ _setModuleDefault = ( this && this . _ _setModuleDefault ) || ( Object . create ? ( function ( o , v ) {
Object . defineProperty ( o , "default" , { enumerable : true , value : v } ) ;
} ) : function ( o , v ) {
o [ "default" ] = v ;
} ) ;
var _ _importStar = ( this && this . _ _importStar ) || function ( mod ) {
if ( mod && mod . _ _esModule ) return mod ;
var result = { } ;
if ( mod != null ) for ( var k in mod ) if ( k !== "default" && Object . prototype . hasOwnProperty . call ( mod , k ) ) _ _createBinding ( result , mod , k ) ;
_ _setModuleDefault ( result , mod ) ;
return result ;
} ;
2023-04-11 18:25:14 +01:00
var _ _importDefault = ( this && this . _ _importDefault ) || function ( mod ) {
return ( mod && mod . _ _esModule ) ? mod : { "default" : mod } ;
} ;
2022-11-22 16:47:27 +00:00
Object . defineProperty ( exports , "__esModule" , { value : true } ) ;
2023-07-19 17:21:33 +01:00
exports . getCheckoutPathInputOrThrow = exports . getUploadInputOrThrow = exports . getCategoryInputOrThrow = exports . getWorkflow = exports . formatWorkflowCause = exports . formatWorkflowErrors = exports . validateWorkflow = exports . getWorkflowErrors = exports . WorkflowErrors = exports . patternIsSuperset = void 0 ;
2022-11-22 16:47:27 +00:00
const fs = _ _importStar ( require ( "fs" ) ) ;
const path = _ _importStar ( require ( "path" ) ) ;
2023-04-11 18:25:14 +01:00
const zlib _1 = _ _importDefault ( require ( "zlib" ) ) ;
2022-11-22 16:47:27 +00:00
const core = _ _importStar ( require ( "@actions/core" ) ) ;
const yaml = _ _importStar ( require ( "js-yaml" ) ) ;
const api = _ _importStar ( require ( "./api-client" ) ) ;
2023-07-06 12:24:38 +01:00
const environment _1 = require ( "./environment" ) ;
2022-11-22 16:47:27 +00:00
const util _1 = require ( "./util" ) ;
2023-05-10 20:37:56 +00:00
function isObject ( o ) {
return o !== null && typeof o === "object" ;
}
2022-11-22 16:47:27 +00:00
const GLOB _PATTERN = new RegExp ( "(\\*\\*?)" ) ;
function escapeRegExp ( string ) {
return string . replace ( /[.*+?^${}()|[\]\\]/g , "\\$&" ) ; // $& means the whole matched string
}
function patternToRegExp ( value ) {
return new RegExp ( ` ^ ${ value
. toString ( )
. split ( GLOB _PATTERN )
. reduce ( function ( arr , cur ) {
if ( cur === "**" ) {
arr . push ( ".*?" ) ;
}
else if (cur === "*") {
arr.push("[^/]*?");
}
else if (cur) {
arr.push(escapeRegExp(cur));
}
return arr;
}, [])
.join("")} $ ` ) ;
}
// this function should return true if patternA is a superset of patternB
// e.g: * is a superset of main-* but main-* is not a superset of *.
function patternIsSuperset ( patternA , patternB ) {
return patternToRegExp ( patternA ) . test ( patternB ) ;
}
exports . patternIsSuperset = patternIsSuperset ;
function toCodedErrors ( errors ) {
2022-11-22 16:57:52 +00:00
return Object . entries ( errors ) . reduce ( ( acc , [ code , message ] ) => {
acc [ code ] = { message , code } ;
2022-11-22 16:47:27 +00:00
return acc ;
} , { } ) ;
}
// code to send back via status report
// message to add as a warning annotation to the run
exports . WorkflowErrors = toCodedErrors ( {
2023-05-11 14:16:34 +00:00
MissingPushHook : ` Please specify an on.push hook to analyze and see code scanning alerts from the default branch on the Security tab. ` ,
2022-11-22 16:47:27 +00:00
CheckoutWrongHead : ` git checkout HEAD^2 is no longer necessary. Please remove this step as Code Scanning recommends analyzing the merge commit for best results. ` ,
} ) ;
2023-09-22 18:55:52 +01:00
/**
* Groups the given list of CodeQL languages by their extractor name.
*
* Resolves to `undefined` if the CodeQL version does not support language aliasing.
*/
async function groupLanguagesByExtractor ( languages , codeql ) {
const resolveResult = await codeql . betterResolveLanguages ( ) ;
if ( ! resolveResult . aliases ) {
return undefined ;
}
const aliases = resolveResult . aliases ;
const languagesByExtractor = { } ;
for ( const language of languages ) {
const extractorName = aliases [ language ] || language ;
if ( ! languagesByExtractor [ extractorName ] ) {
languagesByExtractor [ extractorName ] = [ ] ;
}
languagesByExtractor [ extractorName ] . push ( language ) ;
}
return languagesByExtractor ;
}
2023-09-22 14:54:03 +01:00
async function getWorkflowErrors ( doc , codeql ) {
2022-11-22 16:47:27 +00:00
const errors = [ ] ;
const jobName = process . env . GITHUB _JOB ;
if ( jobName ) {
2023-01-18 20:00:33 +00:00
const job = doc ? . jobs ? . [ jobName ] ;
2023-09-22 14:54:03 +01:00
if ( job ? . strategy ? . matrix ? . language ) {
const matrixLanguages = job . strategy . matrix . language ;
if ( Array . isArray ( matrixLanguages ) ) {
2023-09-22 18:55:52 +01:00
// Map extractors to entries in the `language` matrix parameter. This will allow us to
// detect languages which are analyzed in more than one job.
const matrixLanguagesByExtractor = await groupLanguagesByExtractor ( matrixLanguages , codeql ) ;
// If the CodeQL version does not support language aliasing, then `matrixLanguagesByExtractor`
// will be `undefined`. In this case, we cannot detect duplicate languages in the matrix.
if ( matrixLanguagesByExtractor !== undefined ) {
2023-09-22 14:54:03 +01:00
// Check for duplicate languages in the matrix
for ( const [ extractor , languages ] of Object . entries ( matrixLanguagesByExtractor ) ) {
if ( languages . length > 1 ) {
errors . push ( {
message : ` CodeQL language ' ${ extractor } ' is referenced by more than one entry in the ` +
` 'language' matrix parameter for job ' ${ jobName } '. This may result in duplicate alerts. ` +
` Please edit the 'language' matrix parameter to keep only one of the following: ${ languages
. map ( ( language ) => ` ' ${ language } ' ` )
. join ( ", " ) } . ` ,
code : "DuplicateLanguageInMatrix" ,
} ) ;
}
}
}
}
}
2023-01-18 20:00:33 +00:00
const steps = job ? . steps ;
2022-11-22 16:47:27 +00:00
if ( Array . isArray ( steps ) ) {
for ( const step of steps ) {
// this was advice that we used to give in the README
// we actually want to run the analysis on the merge commit
// to produce results that are more inline with expectations
// (i.e: this is what will happen if you merge this PR)
// and avoid some race conditions
2023-01-18 20:00:33 +00:00
if ( step ? . run === "git checkout HEAD^2" ) {
2022-11-22 16:47:27 +00:00
errors . push ( exports . WorkflowErrors . CheckoutWrongHead ) ;
break ;
}
}
}
}
2023-05-10 20:37:56 +00:00
let missingPush = false ;
if ( doc . on === undefined ) {
// this is not a valid config
}
else if ( typeof doc . on === "string" ) {
if ( doc . on === "pull_request" ) {
missingPush = true ;
}
}
else if ( Array . isArray ( doc . on ) ) {
const hasPush = doc . on . includes ( "push" ) ;
const hasPullRequest = doc . on . includes ( "pull_request" ) ;
if ( hasPullRequest && ! hasPush ) {
missingPush = true ;
}
}
else if ( isObject ( doc . on ) ) {
const hasPush = Object . prototype . hasOwnProperty . call ( doc . on , "push" ) ;
const hasPullRequest = Object . prototype . hasOwnProperty . call ( doc . on , "pull_request" ) ;
if ( ! hasPush && hasPullRequest ) {
missingPush = true ;
}
}
if ( missingPush ) {
errors . push ( exports . WorkflowErrors . MissingPushHook ) ;
}
2022-11-22 16:47:27 +00:00
return errors ;
}
exports . getWorkflowErrors = getWorkflowErrors ;
2023-09-22 14:54:03 +01:00
async function validateWorkflow ( codeql , logger ) {
2022-11-22 16:47:27 +00:00
let workflow ;
try {
2023-04-11 18:25:14 +01:00
workflow = await getWorkflow ( logger ) ;
2022-11-22 16:47:27 +00:00
}
catch ( e ) {
return ` error: getWorkflow() failed: ${ String ( e ) } ` ;
}
let workflowErrors ;
try {
2023-09-22 14:54:03 +01:00
workflowErrors = await getWorkflowErrors ( workflow , codeql ) ;
2022-11-22 16:47:27 +00:00
}
catch ( e ) {
return ` error: getWorkflowErrors() failed: ${ String ( e ) } ` ;
}
if ( workflowErrors . length > 0 ) {
let message ;
try {
message = formatWorkflowErrors ( workflowErrors ) ;
}
catch ( e ) {
return ` error: formatWorkflowErrors() failed: ${ String ( e ) } ` ;
}
core . warning ( message ) ;
}
return formatWorkflowCause ( workflowErrors ) ;
}
exports . validateWorkflow = validateWorkflow ;
function formatWorkflowErrors ( errors ) {
const issuesWere = errors . length === 1 ? "issue was" : "issues were" ;
const errorsList = errors . map ( ( e ) => e . message ) . join ( " " ) ;
return ` ${ errors . length } ${ issuesWere } detected with this workflow: ${ errorsList } ` ;
}
exports . formatWorkflowErrors = formatWorkflowErrors ;
function formatWorkflowCause ( errors ) {
if ( errors . length === 0 ) {
return undefined ;
}
return errors . map ( ( e ) => e . code ) . join ( "," ) ;
}
exports . formatWorkflowCause = formatWorkflowCause ;
2023-04-11 18:25:14 +01:00
async function getWorkflow ( logger ) {
// In default setup, the currently executing workflow is not checked into the repository.
// Instead, a gzipped then base64 encoded version of the workflow file is provided via the
// `CODE_SCANNING_WORKFLOW_FILE` environment variable.
const maybeWorkflow = process . env [ "CODE_SCANNING_WORKFLOW_FILE" ] ;
if ( maybeWorkflow ) {
logger . debug ( "Using the workflow specified by the CODE_SCANNING_WORKFLOW_FILE environment variable." ) ;
return yaml . load ( zlib _1 . default . gunzipSync ( Buffer . from ( maybeWorkflow , "base64" ) ) . toString ( ) ) ;
2022-12-21 11:29:03 +00:00
}
2023-04-11 18:25:14 +01:00
const workflowPath = await getWorkflowAbsolutePath ( logger ) ;
return yaml . load ( fs . readFileSync ( workflowPath , "utf-8" ) ) ;
2022-11-22 16:47:27 +00:00
}
exports . getWorkflow = getWorkflow ;
/**
2023-04-11 18:25:14 +01:00
* Get the absolute path of the currently executing workflow.
*/
async function getWorkflowAbsolutePath ( logger ) {
2023-07-19 17:21:33 +01:00
const relativePath = await api . getWorkflowRelativePath ( ) ;
2023-04-11 18:25:14 +01:00
const absolutePath = path . join ( ( 0 , util _1 . getRequiredEnvParam ) ( "GITHUB_WORKSPACE" ) , relativePath ) ;
if ( fs . existsSync ( absolutePath ) ) {
logger . debug ( ` Derived the following absolute path for the currently executing workflow: ${ absolutePath } . ` ) ;
return absolutePath ;
}
throw new Error ( ` Expected to find a code scanning workflow file at ${ absolutePath } , but no such file existed. ` +
"This can happen if the currently running workflow checks out a branch that doesn't contain " +
"the corresponding workflow file." ) ;
}
2022-11-23 12:43:32 +00:00
function getStepsCallingAction ( job , actionName ) {
2022-12-21 11:13:48 +00:00
if ( job . uses ) {
throw new Error ( ` Could not get steps calling ${ actionName } since the job calls a reusable workflow. ` ) ;
}
2022-11-22 18:15:28 +00:00
const steps = job . steps ;
if ( ! Array . isArray ( steps ) ) {
2022-11-23 12:43:32 +00:00
throw new Error ( ` Could not get steps calling ${ actionName } since job.steps was not an array. ` ) ;
2022-11-22 18:15:28 +00:00
}
2023-01-18 20:00:33 +00:00
return steps . filter ( ( step ) => step . uses ? . includes ( actionName ) ) ;
2022-11-22 18:15:28 +00:00
}
2022-11-22 18:26:14 +00:00
/**
2022-11-23 12:43:32 +00:00
* Makes a best effort attempt to retrieve the value of a particular input with which
* an Action in the workflow would be invoked.
2022-11-22 18:26:14 +00:00
*
2022-11-29 16:29:27 +00:00
* Typically you'll want to wrap this function in a try/catch block and handle the error.
*
2022-11-23 12:43:32 +00:00
* @returns the value of the input, or undefined if no such input is passed to the Action
* @throws an error if the value of the input could not be determined, or we could not
* determine that no such input is passed to the Action.
2022-11-22 18:26:14 +00:00
*/
2022-11-25 17:40:27 +00:00
function getInputOrThrow ( workflow , jobName , actionName , inputName , matrixVars ) {
2022-12-06 18:13:47 +00:00
const preamble = ` Could not get ${ inputName } input to ${ actionName } since ` ;
2022-11-22 18:15:28 +00:00
if ( ! workflow . jobs ) {
2022-12-06 18:13:47 +00:00
throw new Error ( ` ${ preamble } the workflow has no jobs. ` ) ;
2022-11-22 18:15:28 +00:00
}
2022-11-25 17:40:27 +00:00
if ( ! workflow . jobs [ jobName ] ) {
2022-12-06 18:13:47 +00:00
throw new Error ( ` ${ preamble } the workflow has no job named ${ jobName } . ` ) ;
2022-11-25 17:40:27 +00:00
}
2022-12-06 18:13:47 +00:00
const stepsCallingAction = getStepsCallingAction ( workflow . jobs [ jobName ] , actionName ) ;
if ( stepsCallingAction . length === 0 ) {
throw new Error ( ` ${ preamble } the ${ jobName } job does not call ${ actionName } . ` ) ;
2022-11-22 18:15:28 +00:00
}
2022-12-06 18:13:47 +00:00
else if ( stepsCallingAction . length > 1 ) {
throw new Error ( ` ${ preamble } the ${ jobName } job calls ${ actionName } multiple times. ` ) ;
2022-11-22 18:15:28 +00:00
}
2023-01-18 20:00:33 +00:00
let input = stepsCallingAction [ 0 ] . with ? . [ inputName ] ? . toString ( ) ;
2022-12-06 18:13:47 +00:00
if ( input !== undefined && matrixVars !== undefined ) {
2022-12-08 18:36:05 +00:00
// Normalize by removing whitespace
2022-11-23 13:27:16 +00:00
input = input . replace ( /\${{\s+/ , "${{" ) . replace ( /\s+}}/ , "}}" ) ;
2022-12-08 18:36:05 +00:00
// Make a basic attempt to substitute matrix variables
2022-11-23 13:27:16 +00:00
for ( const [ key , value ] of Object . entries ( matrixVars ) ) {
input = input . replace ( ` \$ {{matrix. ${ key } }} ` , value ) ;
}
2022-11-22 18:26:14 +00:00
}
2022-12-06 18:13:47 +00:00
if ( input !== undefined && input . includes ( "${{" ) ) {
2022-11-23 12:43:32 +00:00
throw new Error ( ` Could not get ${ inputName } input to ${ actionName } since it contained an unrecognized dynamic value. ` ) ;
2022-11-22 18:15:28 +00:00
}
2022-11-23 12:43:32 +00:00
return input ;
}
2022-12-06 18:31:52 +00:00
/**
* Get the expected name of the analyze Action.
*
* This allows us to test workflow parsing functionality as a CodeQL Action PR check.
*/
function getAnalyzeActionName ( ) {
2023-05-22 23:58:26 -04:00
if ( ( 0 , util _1 . isInTestMode ) ( ) ||
2023-07-06 12:24:38 +01:00
process . env [ environment _1 . EnvVar . TESTING _ENVIRONMENT ] === "codeql-action-pr-checks" ) {
2022-12-06 18:31:52 +00:00
return "./analyze" ;
}
else {
return "github/codeql-action/analyze" ;
}
}
2022-11-23 12:43:32 +00:00
/**
* Makes a best effort attempt to retrieve the category input for the particular job,
* given a set of matrix variables.
*
2022-11-29 16:29:27 +00:00
* Typically you'll want to wrap this function in a try/catch block and handle the error.
*
2022-11-23 12:43:32 +00:00
* @returns the category input, or undefined if the category input is not defined
* @throws an error if the category input could not be determined
*/
2022-11-25 17:40:27 +00:00
function getCategoryInputOrThrow ( workflow , jobName , matrixVars ) {
2022-12-06 18:31:52 +00:00
return getInputOrThrow ( workflow , jobName , getAnalyzeActionName ( ) , "category" , matrixVars ) ;
2022-11-22 18:15:28 +00:00
}
2022-11-23 12:43:32 +00:00
exports . getCategoryInputOrThrow = getCategoryInputOrThrow ;
2022-11-23 12:48:24 +00:00
/**
* Makes a best effort attempt to retrieve the upload input for the particular job,
* given a set of matrix variables.
*
2022-11-29 16:29:27 +00:00
* Typically you'll want to wrap this function in a try/catch block and handle the error.
*
2023-03-23 10:23:25 -07:00
* @returns the user input to upload, or undefined if input was unspecified
2022-11-23 12:48:24 +00:00
* @throws an error if the upload input could not be determined
*/
function getUploadInputOrThrow ( workflow , jobName , matrixVars ) {
2023-03-23 10:23:25 -07:00
return getInputOrThrow ( workflow , jobName , getAnalyzeActionName ( ) , "upload" , matrixVars ) ;
2022-11-23 12:48:24 +00:00
}
exports . getUploadInputOrThrow = getUploadInputOrThrow ;
2022-11-23 13:27:16 +00:00
/**
* Makes a best effort attempt to retrieve the checkout_path input for the
* particular job, given a set of matrix variables.
*
2022-11-29 16:29:27 +00:00
* Typically you'll want to wrap this function in a try/catch block and handle the error.
*
2022-11-23 13:27:16 +00:00
* @returns the checkout_path input
* @throws an error if the checkout_path input could not be determined
*/
function getCheckoutPathInputOrThrow ( workflow , jobName , matrixVars ) {
2022-12-06 18:31:52 +00:00
return ( getInputOrThrow ( workflow , jobName , getAnalyzeActionName ( ) , "checkout_path" , matrixVars ) || ( 0 , util _1 . getRequiredEnvParam ) ( "GITHUB_WORKSPACE" ) // if unspecified, checkout_path defaults to ${{ github.workspace }}
2022-11-23 13:27:16 +00:00
) ;
}
exports . getCheckoutPathInputOrThrow = getCheckoutPathInputOrThrow ;
2022-11-22 16:47:27 +00:00
//# sourceMappingURL=workflow.js.map