Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release pr check #33

Merged
merged 16 commits into from
Nov 29, 2023
Merged
82 changes: 82 additions & 0 deletions .github/workflows/handle-versioning-accross-branches.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Handle versioning accros branches

on:
push:
# todo: add file paths of the files with version numbers

jobs:
extension-versioning:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: git-actions/set-user@v1

- name: Prevent branch warnings
run: |
# config git advice.detachedHead to false
git config advice.detachedHead false

- uses: actions/setup-node@v3
with:
node-version: 16

- name: Install tfx extension
run: |
npm install -g tfx-cli

- name: Get highest version number accross all branches
id: get-version
shell: pwsh
env:
AZURE_DEVOPS_CREATE_PAT: ${{ secrets.AZURE_DEVOPS_CREATE_PAT}}
run: |
# get the last updated version for this extension from the server
$output = $(tfx extension show --token $env:AZURE_DEVOPS_CREATE_PAT --vsix $vsix --publisher "RobBos" --extension-id "GHAzDoWidget-DEV" --output json | ConvertFrom-Json)
$lastVersion = ($output.versions | Sort-Object -Property lastUpdated -Descending)[0]
Write-Host "Last version: [$($lastVersion.version)] from server"

# SemVer code
function Parse-SemVer ($version) {
$parts = $version.Split('.')
return @{
Major = [int]$parts[0]
Minor = [int]$parts[1]
Patch = [int]$parts[2]
}
}

$highestVersion = @{
Major = 0
Minor = 0
Patch = 0
}

# loop over all branches
$highestVersion = 0
foreach ($branch in $(git branch -r --format='%(refname:short)')) {
Write-Host "Checkout the branch [$branch]"
git checkout $branch

# get the semantic version number from the version in the dependencyReviewTask/task.json file
$version = Get-Content -Path "dependencyReviewTask/task.json" | ConvertFrom-Json | Select-Object -ExpandProperty version
Write-Host "Found version: [$version] in branch: [$branch]"

# check if the version is semantically higher than the highest version using SemVer
if ($version.Major -gt $highestVersion.Major -or
($version.Major -eq $highestVersion.Major -and $version.Minor -gt $highestVersion.Minor) -or
($version.Major -eq $highestVersion.Major -and $version.Minor -eq $highestVersion.Minor -and $version.Patch -gt $highestVersion.Patch))
{
$highestVersion = $version

Write-Host "New highest version from PR task.json: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]"
}
}

Write-Host "Highest version: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]"

# show the highest version number in GitHub by writing to the job summary file
Set-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Highest version of the extension: [$($lastVersion.version)]"
Set-Content -Path $env:GITHUB_STEP_SUMMARY -Value "Highest version of the PR check extension: [$($highestVersion.Major).$($highestVersion.Minor).$($highestVersion.Patch)]"
206 changes: 135 additions & 71 deletions dependencyReviewTask/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,35 @@ interface IResponse {
result: IResult;
}

async function getAlerts(connection: WebApi, orgSlug: string, project: string, repository: string, branchName: string) {
const branchUrl = `https://advsec.dev.azure.com/${orgSlug}/${project}/_apis/AdvancedSecurity/repositories/${repository}/alerts?criteria.alertType=1&criteria.ref=${branchName}&criteria.onlyDefaultBranchAlerts=true&useDatabaseProvider=true`;
async function getAlerts(
connection: WebApi,
orgSlug: string,
project: string,
repository: string,
branchName: string,
alertType: number
)
{
if (!(alertType == 1 || alertType == 3)) {
console.log(`Error loading alerts for branch [${branchName}] with unknown alertType [${alertType}]`)
return null
}

const branchUrl = `https://advsec.dev.azure.com/${orgSlug}/${project.replace(" ", "%20")}/_apis/alert/repositories/${repository}/alerts?criteria.alertType=${alertType}&criteria.ref=${branchName}&criteria.onlyDefaultBranchAlerts=true&useDatabaseProvider=true`
tl.debug(`Calling api with url: [${branchUrl}]`)

let branchResponse: IResponse

try {
branchResponse = await connection.rest.get<IResult>(branchUrl);
branchResponse = await connection.rest.get<IResult>(branchUrl)
}
catch (err: unknown) {
if (err instanceof Error) {
if (err.message.includes('Branch does not exist')) {
console.log(`Branch [${branchName}] does not exist in GHAzDo yet. Make sure to run the Dependency Scan task first on this branch (easiest to do in the same pipeline).`);
console.log(`Branch [${branchName}] does not exist in GHAzDo yet. Make sure to run the Dependency Scan task first on this branch (easiest to do in the same pipeline).`)
}
else {
console.log(`An error occurred: ${err.message}`);
console.log(`An error occurred: ${err.message}`)
}
}
}
Expand All @@ -49,87 +64,136 @@ async function getAlerts(connection: WebApi, orgSlug: string, project: string, r
async function run() {
try {
// test to see if this build was triggered with a PR context
const buildReason = tl.getVariable('Build.Reason');
const buildReason = tl.getVariable('Build.Reason')
if (buildReason != 'PullRequest') {
tl.setResult(tl.TaskResult.Skipped, `This extension only works when triggered by a Pull Request and not by a [${buildReason}]`);
tl.setResult(tl.TaskResult.Skipped, `This extension only works when triggered by a Pull Request and not by a [${buildReason}]`)
return
}

// todo: convert to some actual setting
const inputString: string | undefined = tl.getInput('samplestring', true);
if (inputString == 'bad') {
tl.setResult(tl.TaskResult.Failed, 'Bad input was given');

// stop the task execution
return;
// todo: convert to some actual value | boolean setting, for example severity score or switch between Dependency and CodeQL alerts
const scanForDependencyAlerts : string | undefined = tl.getInput('DepedencyAlertsScan', true)
tl.debug(`scanForDependencyAlerts setting value: ${scanForDependencyAlerts}`)

const scanForCodeScanningAlerts : string | undefined = tl.getInput('CodeScanningAlerts', true)
tl.debug(`scanForCodeScanningAlerts setting value: ${scanForCodeScanningAlerts}`)

const token = getSystemAccessToken()
const authHandler = getHandlerFromToken(token)
const uri = tl.getVariable("System.CollectionUri")
const connection = new WebApi(uri, authHandler)

const organization = tl.getVariable('System.TeamFoundationCollectionUri')
const orgSlug = organization.split('/')[3]
const project = tl.getVariable('System.TeamProject')
const repository = tl.getVariable('Build.Repository.ID')
const sourceBranch = tl.getVariable('System.PullRequest.SourceBranch')
const sourceBranchName = sourceBranch?.split('/')[2]
const targetBranchName = tl.getVariable('System.PullRequest.targetBranchName')

let alertType = 0
let errorString = ""
console.log(`Retrieving alerts with token: [${token}], organization: [${organization}], orgSlug: [${orgSlug}], project: [${project}], sourceBranchName: [${sourceBranchName}], targetBranchName: [${targetBranchName}]`)
if (scanForDependencyAlerts == 'true') {
alertType = 1 // Dependency Scanning alerts
const dependencyResult = await checkAlertsForType(connection, orgSlug, project, repository, alertType, sourceBranchName, targetBranchName)
if (dependencyResult.newAlertsFound) {
errorString += dependencyResult.message
}
}
console.log('Hello', inputString);

const token = getSystemAccessToken();
const authHandler = getHandlerFromToken(token);
const uri = tl.getVariable("System.CollectionUri");
const connection = new WebApi(uri, authHandler);
if (scanForCodeScanningAlerts == 'true') {
alertType = 3 // Code Scanning alerts
const codeScanningResult = await checkAlertsForType(connection, orgSlug, project, repository, alertType, sourceBranchName, targetBranchName)
if (codeScanningResult.newAlertsFound) {
errorString += codeScanningResult.message
}
}

const organization = tl.getVariable('System.TeamFoundationCollectionUri');
const orgSlug = organization.split('/')[3];
const project = tl.getVariable('System.TeamProject');
const repository = tl.getVariable('Build.Repository.ID');
const sourceBranch = tl.getVariable('System.PullRequest.SourceBranch');
const sourceBranchName = sourceBranch?.split('/')[2];
const targetBranchName = tl.getVariable('System.PullRequest.targetBranchName');
if (scanForDependencyAlerts !== 'true' && scanForCodeScanningAlerts !== 'true') {
const message = `No options selected to check for either dependency scanning alerts or code scanning alerts`
console.log(message)
tl.setResult(tl.TaskResult.Skipped, message)
return
}

console.log(`Retrieving alerts with token: [${token}], organization: [${organization}], orgSlug: [${orgSlug}], project: [${project}], sourceBranchName: [${sourceBranchName}], targetBranchName: [${targetBranchName}]`);
if (errorString.length > 0) {
tl.setResult(tl.TaskResult.Failed, errorString)
}
}
catch (err: unknown) {
if (err instanceof Error) {
tl.setResult(tl.TaskResult.Failed, err.message)
} else {
tl.setResult(tl.TaskResult.Failed, 'An unknown error occurred')
}
}

const sourceBranchResponse = await getAlerts(connection, orgSlug, project, repository, sourceBranchName);
const targetBranchResponse = await getAlerts(connection, orgSlug, project, repository, targetBranchName);
// everything worked, no new alerts found and at least one scanning option was enabled
tl.setResult(tl.TaskResult.Succeeded)
}

tl.debug(`source response: ${JSON.stringify(sourceBranchResponse)}`);
tl.debug(`target response: ${JSON.stringify(targetBranchResponse)}`);
async function checkAlertsForType(
connection: WebApi,
orgSlug: string,
project: string,
repository: string,
alertType: number,
sourceBranchName: string,
targetBranchName: string
): Promise<{newAlertsFound: boolean, message: string}>
{
const sourceBranchResponse = await getAlerts(connection, orgSlug, project, repository, sourceBranchName, alertType)
const targetBranchResponse = await getAlerts(connection, orgSlug, project, repository, targetBranchName, alertType)

// todo: check if response.statuscode === 404 and skip the rest, do report a warning
tl.debug(`source response: ${JSON.stringify(sourceBranchResponse)}`)
tl.debug(`target response: ${JSON.stringify(targetBranchResponse)}`)

let alertTypeString = `Dependency`
if (alertType == 3) {
alertTypeString = `Code scanning`
}

if (sourceBranchResponse.result.count == 0) {
console.log('No alerts found for this branch');
if (!sourceBranchResponse || sourceBranchResponse?.result?.count == 0) {
console.log(`No alerts found for this branch [${sourceBranchName}] for alert type [${alertTypeString}]`)

tl.setResult(tl.TaskResult.Succeeded, `Found no alerts for the source branch`);
return;
}
else {
// check by result.alertId if there is a new alert or not (so alert not in targetBranch)

// first get the only the alertid's from the source branch
const sourceAlertIds = sourceBranchResponse.result.value.map((alert) => {return alert.alertId;});
// do the same for the target branch
const targetAlertIds = targetBranchResponse.result.value.map((alert) => {return alert.alertId;});
// now find the delta
const newAlertIds = sourceAlertIds.filter((alertId) => {
return !targetAlertIds.includes(alertId);
});

if (newAlertIds.length > 0) {

console.log(`Found [${sourceBranchResponse.result.count}] alerts for the source branch [${sourceBranchName}] of which [${newAlertIds.length}] are new:`);
for (const alertId of newAlertIds) {
// get the alert details:
const alertUrl = `https://dev.azure.com/${orgSlug}/${project}/_git/${repository}/alerts/${alertId}?branch=refs/heads/${sourceBranchName}`;
const alertTitle = sourceBranchResponse.result.value.find((alert) => {return alert.alertId == alertId;})?.title;
// and show them:
console.log(`- ${alertId}: ${alertTitle}, url: ${alertUrl}`);
}

tl.setResult(tl.TaskResult.Failed, `Found [${sourceBranchResponse.result.count}] alerts for the source branch [${sourceBranchName}] of which [${newAlertIds.length}] are new`);
}
else {
console.log(`Found no new alerts for the source branch [${sourceBranchName}]`);
tl.setResult(tl.TaskResult.Succeeded, `Found no new alerts for the source branch [${sourceBranchName}], only [${targetBranchResponse.result.count}] existing ones`);
//tl.setResult(tl.TaskResult.Succeeded, `Found no alerts for the source branch`)
return { newAlertsFound: false, message: `` }
}
else {
// check by result.alertId if there is a new alert or not (so alert not in targetBranch)

// first get the only the alertid's from the source branch
const sourceAlertIds = sourceBranchResponse.result.value.map((alert) => {return alert.alertId;})
// do the same for the target branch
const targetAlertIds = targetBranchResponse.result.value.map((alert) => {return alert.alertId;})
// now find the delta
const newAlertIds = sourceAlertIds.filter((alertId) => {
return !targetAlertIds.includes(alertId)
});

if (newAlertIds.length > 0) {
let message =`Found [${sourceBranchResponse.result.count}] alerts for the source branch [${sourceBranchName}] for alert type [${alertTypeString}] of which [${newAlertIds.length}] are new:`
console.log(message)
for (const alertId of newAlertIds) {
// get the alert details:
const alertUrl = `https://dev.azure.com/${orgSlug}/${project.replace(" ", "%20")}/_git/${repository}/alerts/${alertId}?branch=refs/heads/${sourceBranchName}`
const alertTitle = sourceBranchResponse.result.value.find((alert) => {return alert.alertId == alertId;})?.title
// and show them:
const specificAlertMessage = `- ${alertId}: ${alertTitle}, url: ${alertUrl}`
console.log(specificAlertMessage)
message += `\r\n${specificAlertMessage}` // todo: check if this new line actually works :-)
// tested \\n --> did not work
// tested \\r\\n --> did not work
}
return {newAlertsFound: true, message: message}
}
}
catch (err: unknown) {
if (err instanceof Error) {
tl.setResult(tl.TaskResult.Failed, err.message);
} else {
tl.setResult(tl.TaskResult.Failed, 'An unknown error occurred');
else {
const message = `Found no new alerts for the source branch [${sourceBranchName}] for alert type [${alertTypeString}]`
console.log(message)
return {newAlertsFound: false, message: message}
}
}
}

run();
run()
28 changes: 18 additions & 10 deletions dependencyReviewTask/task.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
{
"$schema": "https://raw.githubusercontent.com/Microsoft/azure-pipelines-task-lib/master/tasks.schema.json",
"id": "10c1d88a-9d0f-4288-8e37-58762caa0b8b",
"name": "Advanced-Dependency-Review",
"friendlyName": "Advanced Security Dependency Review",
"description": "Scan the source branch in your PR for known Dependency issues",
"helpMarkDown": "Checks the source branch in your PR for known Dependency issues",
"name": "Advanced-Security-Review",
"friendlyName": "Advanced Security Review",
"description": "Scan the source branch in your PR for known Advanced Security issues",
"helpMarkDown": "Checks the source branch in your PR for known Advanced Security issues",
"category": "Utility",
"author": "RobBos",
"version": {
"Major": 0,
"Minor": 1,
"Patch": 23
"Patch": 37
},
"instanceNameFormat": "Echo $(samplestring)",
"inputs": [
{
"name": "samplestring",
"type": "string",
"label": "Sample String",
"defaultValue": "",
"name": "DepedencyAlertsScan",
"type": "boolean",
"label": "Fail on new dependency alerts",
"defaultValue": true,
"required": true,
"helpMarkDown": "A sample string"
"helpMarkDown": "Fail the pipeline if there is a new dependency alert"
},
{
"name": "CodeScanningAlerts",
"type": "boolean",
"label": "Fail on new code scanning alerts",
"defaultValue": true,
"required": true,
"helpMarkDown": "Fail the pipeline if there is a new code scanning alert"
}
],
"execution": {
Expand Down
Binary file added img/dependencyReviewTask.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading