├── .github └── workflows │ └── workflow.createprcomment.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CreatePrComment ├── .gitignore ├── CONTRIBUTION.md ├── LICENSE ├── LICENSE.md ├── README.md ├── docs │ └── images │ │ ├── Comment.png │ │ ├── CommentTask.png │ │ ├── CreatePRCommentTask.png │ │ └── ServiceConnection.png ├── package-lock.json ├── package.json ├── src │ ├── task.ts │ └── variableresolver.ts ├── task │ ├── CreatePRCommentTaskV0 │ │ ├── icon.png │ │ └── task.json │ └── CreatePRCommentTaskV1 │ │ ├── icon.png │ │ └── task.json ├── tests │ └── L0.ts ├── tsconfig.json ├── vss-extension-icon.png └── vss-extension.json ├── LICENSE ├── PipelineRunnerExtension ├── README.MD └── images │ ├── connection-icon.png │ ├── icon.png │ ├── screenshot-1.png │ ├── screenshot-2.png │ ├── screenshot-3.png │ └── screenshot-4.png ├── README.md ├── SECURITY.md └── Zap ├── README.md └── docs └── images ├── scan-results-collapsed.png ├── scan-results-drill-down.png ├── zap-aggressive.png ├── zap-context-empty.png ├── zap-context-provided.png ├── zap-scanner-config.png ├── zap-scanner-task.png └── zap-target.png /.github/workflows/workflow.createprcomment.yml: -------------------------------------------------------------------------------- 1 | name: 'CreatePrComment' 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | paths: 9 | - "CreatePrComment/**" 10 | workflow_dispatch: 11 | 12 | env: 13 | WORKDIR: "CreatePrComment" 14 | 15 | defaults: 16 | run: 17 | working-directory: "CreatePrComment" 18 | 19 | jobs: 20 | build: 21 | strategy: 22 | matrix: 23 | os: 24 | - ubuntu-latest 25 | - macos-latest 26 | - windows-latest 27 | node-version: [10, 14] 28 | runs-on: ${{ matrix.os }} 29 | steps: 30 | - uses: actions/checkout@v2 31 | - name: "Use Node.js ${{ matrix.node-version }} on ${{ matrix.os }}" 32 | uses: actions/setup-node@v2 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | cache: npm 36 | cache-dependency-path: "${{ env.WORKDIR }}/package-lock.json" 37 | - run: npm ci 38 | - run: npm run build --if-present 39 | - run: npm run test --if-present 40 | - run: npm run report --if-present 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /CreatePrComment/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.js 3 | *.vsix 4 | *.map 5 | .vscode 6 | test-results.xml 7 | .nyc_output 8 | .coverage_output 9 | .bin 10 | .dist 11 | .taskkey -------------------------------------------------------------------------------- /CreatePrComment/CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | # Contribution 2 | 3 | This article explains how to build an development environment. 4 | 5 | ## Prerequisite 6 | 7 | - Node.js 14.0.x or later and NPM 8 | - [TFS Cross Platform Command Line Interface(tfx-cli)](https://github.com/microsoft/tfs-cli) 0.9.3 or latter 9 | - [Typescript](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) 4.4.4 or latter 10 | 11 | ## installation 12 | 13 | Clone this repo. Then install the npm packages. 14 | 15 | ```shell 16 | npm install 17 | ``` 18 | 19 | ## npm commands 20 | 21 | ### Build 22 | 23 | Compile the TypeScript files to the js file. 24 | 25 | ```shell 26 | npm run build 27 | ``` 28 | 29 | ### Pack 30 | 31 | Compile and Pack the code to the task directory 32 | 33 | ```shell 34 | npm run pack 35 | ``` 36 | 37 | ### Create 38 | 39 | Compile, Pack and Create an extension `vsix` file. 40 | 41 | ```shell 42 | npm run create 43 | ``` 44 | 45 | ### Test 46 | 47 | Currently Not Supported, however, it is coming soon. 48 | -------------------------------------------------------------------------------- /CreatePrComment/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /CreatePrComment/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) Microsoft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CreatePrComment/README.md: -------------------------------------------------------------------------------- 1 | # Create PR Comment Task 2 | 3 | ![Comment](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/CreatePrComment/docs/images/Comment.png) 4 | 5 | Create a Pull Request comment if a CI is triggered by Pull Request. 6 | 7 | ## How to use 8 | 9 | ### Configuration 10 | 11 | Install this extension to your project. Find the CreatePRCommentTask. 12 | 13 | ![CreatePRCommentTask](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/CreatePrComment/docs/images/CreatePRCommentTask.png) 14 | 15 | ### Details 16 | 17 | ![Task details](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/CreatePrComment/docs/images/CommentTask.png) 18 | 19 | | Name | Description | 20 | | ---------------- | ------------------------------------------------------------------------------------------------ | 21 | | Azure DevOps PAT | Select Azure DevOps Personal Access Token. or you can create new one | 22 | | Comment | If the pipeline is executed by Pull Request Validation, this task create a Pull Request Comment. | 23 | 24 | On the Comment, you can use Variables. The variables will be substituted by the actual value. e.g. `$(CWI.Id)`. 25 | The comment becomes message body of your Pull Request Comment. 26 | 27 | ### Personal Access Token Service Connection 28 | 29 | Put your Azure DevOps Personal Access Token in `PAT`. The PAT requires permission to write Code. For more detail, [Pull Request Thread Comments - Create](https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull-request-thread-comments/create?view=azure-devops-rest-6.1). `Connection name` is just a label of this service connection. `Server URL` is not used currently, however it might be good as memo which you use it for. 30 | 31 | ![ServiceConnection](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/CreatePrComment/docs/images/ServiceConnection.png) 32 | 33 | ### Example 34 | 35 | Sample of the Comment. 36 | 37 | ```text 38 | CredScan reports a Bug. Please review it. 39 | ``` 40 | 41 | ## Contribution 42 | 43 | For more details [here](https://github.com/microsoft/CSEDevOps/blob/main/CreatePrComment/CONTRIBUTION.md). 44 | -------------------------------------------------------------------------------- /CreatePrComment/docs/images/Comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/CreatePrComment/docs/images/Comment.png -------------------------------------------------------------------------------- /CreatePrComment/docs/images/CommentTask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/CreatePrComment/docs/images/CommentTask.png -------------------------------------------------------------------------------- /CreatePrComment/docs/images/CreatePRCommentTask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/CreatePrComment/docs/images/CreatePRCommentTask.png -------------------------------------------------------------------------------- /CreatePrComment/docs/images/ServiceConnection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/CreatePrComment/docs/images/ServiceConnection.png -------------------------------------------------------------------------------- /CreatePrComment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-pr-comment-task", 3 | "version": "1.0.0", 4 | "description": "Create a comment on a Pull Request", 5 | "main": "task.js", 6 | "scripts": { 7 | "clean": "rimraf ./.bin && rimraf ./.dist && rimraf ./*.vsix", 8 | "deps": "rimraf ./node_modules && npm install", 9 | "build": "rimraf ./.bin && tsc -p .", 10 | "start": "copyfiles -f ./task/CreatePRCommentTaskV1/task.json ./src && ts-node ./src/task.ts && rimraf ./src/task.json", 11 | "pack": "rimraf ./.dist && copyfiles vss-extension.json vss-extension-icon.png LICENSE.md README.md \"./task/**\" ./.dist && copyfiles -f package.json \"./.bin/*.js\" \"./.bin/**/*.js\" -e \"./.bin/tests/*\" ./.dist/task/CreatePRCommentTaskV0 && copyfiles -f package.json \"./.bin/*.js\" \"./.bin/**/*.js\" -e \"./.bin/tests/*\" ./.dist/task/CreatePRCommentTaskV1 && loop \"npm install --only=prod\" --cwd ./.dist/task", 12 | "create": "tfx extension create -r ./.dist", 13 | "createdev": "tfx extension create --rev-version --root ./.dist --publisher daporo-dev", 14 | "test": "tsc -p . && copyfiles -f ./task/CreatePRCommentTaskV1/task.json ./.bin/src && nyc mocha ./.bin/tests/L0.js", 15 | "report": "tsc -p . && mocha ./.bin/tests/L0.js --reporter mocha-junit-reporter && nyc report", 16 | "lint": "ts-standard", 17 | "lintfix": "ts-standard --fix" 18 | }, 19 | "nyc": { 20 | "extension": [ 21 | ".ts" 22 | ], 23 | "include": [ 24 | "src" 25 | ], 26 | "reporter": [ 27 | "text", 28 | "cobertura", 29 | "html" 30 | ], 31 | "report-dir": "./.coverage_output/coverage", 32 | "all": "true", 33 | "check-coverage": true, 34 | "statements": 70, 35 | "functions": 70, 36 | "branches": 70, 37 | "lines": 70 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/microsoft/CSEDevOps.git" 42 | }, 43 | "keywords": [ 44 | "Pull Request", 45 | "PR", 46 | "PR Comment", 47 | "Utility task", 48 | "Utility", 49 | "Azure Pipelines" 50 | ], 51 | "author": "CSE-DevOps", 52 | "license": "MIT", 53 | "bugs": { 54 | "url": "https://github.com/microsoft/CSEDevOps/issues?q=is:issue+label:CreatePrComment" 55 | }, 56 | "homepage": "https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment", 57 | "dependencies": { 58 | "azure-devops-node-api": "^11.1.0", 59 | "azure-pipelines-task-lib": "^3.1.10" 60 | }, 61 | "devDependencies": { 62 | "@types/chai": "^4.3.0", 63 | "@types/mocha": "^9.1.0", 64 | "@types/node": "^17.0.18", 65 | "@types/q": "^1.5.5", 66 | "@types/sinon": "^10.0.11", 67 | "chai": "^4.3.6", 68 | "copyfiles": "^2.4.1", 69 | "loop": "^3.3.6", 70 | "mocha": "^9.2.0", 71 | "mocha-junit-reporter": "^2.0.2", 72 | "nyc": "^15.1.0", 73 | "rewiremock": "^3.14.3", 74 | "rimraf": "^3.0.2", 75 | "sinon": "^13.0.1", 76 | "source-map-support": "^0.5.21", 77 | "tfx-cli": "^0.10.0", 78 | "ts-node": "^10.5.0", 79 | "typescript": "^4.5.5" 80 | } 81 | } -------------------------------------------------------------------------------- /CreatePrComment/src/task.ts: -------------------------------------------------------------------------------- 1 | import * as tl from 'azure-pipelines-task-lib' 2 | import * as wa from 'azure-devops-node-api/WebApi' 3 | import * as GitInterfaces from 'azure-devops-node-api/interfaces/GitInterfaces' 4 | import VariableResolver from './variableresolver' 5 | import { IGitApi, GitApi } from 'azure-devops-node-api/GitApi' 6 | import path from 'path' 7 | import { IRequestHandler } from 'azure-devops-node-api/interfaces/common/VsoBaseInterfaces' 8 | 9 | export interface IClientFactory { 10 | create: () => Promise 11 | } 12 | 13 | class ClientFactory implements IClientFactory { 14 | public async create (): Promise { 15 | const authType = tl.getInput('AuthType') || 'patService' // default for V0 16 | let credHandler: IRequestHandler 17 | 18 | switch (authType) { 19 | case 'patService': { 20 | const patService = tl.getInput('AzureDevOpsService')! 21 | const pat = tl.getEndpointAuthorizationParameter(patService, 'pat', false)! 22 | credHandler = wa.getPersonalAccessTokenHandler(pat) 23 | break 24 | } 25 | case 'pat': { 26 | const pat = tl.getInput('AzureDevOpsPat')! 27 | credHandler = wa.getPersonalAccessTokenHandler(pat) 28 | break 29 | } 30 | case 'system': { 31 | const token = tl.getVariable('System.AccessToken')! 32 | credHandler = wa.getBearerHandler(token) 33 | break 34 | } 35 | default: 36 | throw 'Unknown authentication type' 37 | } 38 | 39 | const connection = new wa.WebApi(tl.getVariable('System.TeamFoundationCollectionUri')!, credHandler) 40 | return await connection.getGitApi() 41 | } 42 | } 43 | 44 | export class CreatePRCommentTask { 45 | factory: IClientFactory 46 | 47 | constructor (clientFactory: IClientFactory) { 48 | this.factory = clientFactory 49 | } 50 | 51 | public async run (): Promise { 52 | try { 53 | tl.setResourcePath(path.join(__dirname, 'task.json'), true) 54 | const commentOriginal = tl.getInput('Comment', true)! 55 | tl.debug('commentOriginal:' + commentOriginal) 56 | const comment = VariableResolver.resolveVariables(commentOriginal) 57 | tl.debug('comment:' + comment) 58 | 59 | const client = await this.factory.create() 60 | 61 | const commentObject = { 62 | content: comment, 63 | commentType: GitInterfaces.CommentType.System 64 | } 65 | const thread: GitInterfaces.GitPullRequestCommentThread = { 66 | comments: [ 67 | commentObject 68 | ], 69 | status: GitInterfaces.CommentThreadStatus.ByDesign 70 | } 71 | const repositoryId = tl.getVariable('Build.Repository.ID')! 72 | const pullRequestIdString = tl.getVariable('System.PullRequest.PullRequestId') 73 | 74 | if (pullRequestIdString === undefined) { 75 | // If the build is not pull request, do nothing. 76 | return 77 | } 78 | 79 | const pullRequestId: number = pullRequestIdString ? parseInt(pullRequestIdString) : 0 80 | 81 | const currentThreads = await client.getThreads(repositoryId, pullRequestId) 82 | for (var currentThread of currentThreads) { 83 | if (currentThread.comments !== null && currentThread.comments !== undefined) { 84 | for (var threadComment of currentThread.comments) { 85 | if (threadComment.content === comment) { 86 | return // If the same comment is already there. 87 | } 88 | } 89 | } 90 | } 91 | 92 | if (pullRequestId != 0) { 93 | const createdThread = await client.createThread(thread, repositoryId, pullRequestId) 94 | } 95 | } catch (e) { 96 | throw new Error(tl.loc('FailToCreateComment', e)) 97 | } 98 | } 99 | } 100 | 101 | new CreatePRCommentTask(new ClientFactory()).run() 102 | -------------------------------------------------------------------------------- /CreatePrComment/src/variableresolver.ts: -------------------------------------------------------------------------------- 1 | import { getVariable, debug } from 'azure-pipelines-task-lib' 2 | 3 | export default class VariableResolver { 4 | private static readonly variableRegExp = /\$\(([^)]+)\)/g 5 | 6 | public static resolveVariables (origValue: string): string { 7 | let newValue = origValue 8 | 9 | let match: RegExpExecArray | null 10 | while ((match = this.variableRegExp.exec(newValue)) !== null) { 11 | const variableValue = getVariable(match[1]) 12 | if (variableValue && variableValue !== '') { 13 | newValue = this.replaceAll(newValue, match[0], variableValue) 14 | this.variableRegExp.lastIndex = 0 15 | } else { 16 | debug('Variable \'' + match[1] + '\' not defined.') 17 | } 18 | } 19 | 20 | return newValue 21 | } 22 | 23 | private static escapeRegExp (expression: string): string { 24 | return expression.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1') 25 | } 26 | 27 | private static replaceAll (origValue: string, searchValue: string, replaceValue: string) { 28 | return origValue.replace(new RegExp(this.escapeRegExp(searchValue), 'g'), replaceValue) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CreatePrComment/task/CreatePRCommentTaskV0/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/CreatePrComment/task/CreatePRCommentTaskV0/icon.png -------------------------------------------------------------------------------- /CreatePrComment/task/CreatePRCommentTaskV0/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0fef745c-60e7-4f36-8541-e4d9ce6729b1", 3 | "name": "CreatePRCommentTask", 4 | "friendlyName": "Create PR comment task", 5 | "description": "Create a Pull Request comment from pipeline", 6 | "helpMarkDown": "[CreatePrComment](https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment)", 7 | "category": "Utility", 8 | "author": "CSE-DevOps", 9 | "preview": true, 10 | "version": { 11 | "Major": 0, 12 | "Minor": 1, 13 | "Patch": 15 14 | }, 15 | "instanceNameFormat": "Create PR Comment", 16 | "inputs": [ 17 | { 18 | "name": "AzureDevOpsService", 19 | "type": "connectedService:azuredevops", 20 | "label": "Azure DevOps PAT", 21 | "required": true, 22 | "helpMarkDown": "Select the Azure DevOps PAT" 23 | }, 24 | { 25 | "name": "Comment", 26 | "type": "multiLine", 27 | "label": "Comment", 28 | "required": true, 29 | "helpMarkDown": "Comment which is created as an Pull Request comment" 30 | } 31 | ], 32 | "execution": { 33 | "Node10": { 34 | "target": "task.js" 35 | } 36 | }, 37 | "messages": { 38 | "FailToCreateComment": "Failed to create a comment. For more details: %s" 39 | } 40 | } -------------------------------------------------------------------------------- /CreatePrComment/task/CreatePRCommentTaskV1/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/CreatePrComment/task/CreatePRCommentTaskV1/icon.png -------------------------------------------------------------------------------- /CreatePrComment/task/CreatePRCommentTaskV1/task.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0fef745c-60e7-4f36-8541-e4d9ce6729b1", 3 | "name": "CreatePRCommentTask", 4 | "friendlyName": "Create PR comment task", 5 | "description": "Create a Pull Request comment from pipeline", 6 | "helpMarkDown": "[CreatePrComment](https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment)", 7 | "category": "Utility", 8 | "author": "CSE-DevOps", 9 | "preview": true, 10 | "version": { 11 | "Major": 1, 12 | "Minor": 0, 13 | "Patch": 0 14 | }, 15 | "instanceNameFormat": "Create PR Comment", 16 | "inputs": [ 17 | { 18 | "name": "AuthType", 19 | "label": "Authentication type", 20 | "type": "pickList", 21 | "defaultValue": "system", 22 | "required": true, 23 | "helpMarkDown": "Use personal or system authorization token", 24 | "properties": { 25 | "editableOptions": false 26 | }, 27 | "options": { 28 | "system": "System Access Token", 29 | "pat": "Personal Access Token" 30 | } 31 | }, 32 | { 33 | "name": "AzureDevOpsPat", 34 | "type": "string", 35 | "label": "Azure DevOps PAT", 36 | "required": true, 37 | "helpMarkDown": "Provide the Azure DevOps PAT", 38 | "visibleRule": "AuthType = pat" 39 | }, 40 | { 41 | "name": "Comment", 42 | "type": "multiLine", 43 | "label": "Comment", 44 | "required": true, 45 | "helpMarkDown": "Comment which is created as an Pull Request comment" 46 | } 47 | ], 48 | "execution": { 49 | "Node10": { 50 | "target": "task.js" 51 | } 52 | }, 53 | "messages": { 54 | "FailToCreateComment": "Failed to create a comment. For more details: %s" 55 | } 56 | } -------------------------------------------------------------------------------- /CreatePrComment/tests/L0.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert' 2 | import * as sinon from 'sinon' 3 | import rewiremock from 'rewiremock' 4 | import { should as Should, expect } from 'chai' 5 | import { IGitApi, GitApi } from 'azure-devops-node-api/GitApi' 6 | import * as GitInterfaces from 'azure-devops-node-api/interfaces/GitInterfaces' 7 | 8 | var should = Should() 9 | 10 | const debugMessages: string[] = [] 11 | let variables: {[key: string]: string} = {} 12 | const inputs: {[key: string]: string} = {} 13 | 14 | rewiremock('azure-pipelines-task-lib') 15 | .with({ 16 | debug: sinon.stub().callsFake(m => debugMessages.push(m)), 17 | getInput: sinon.stub().callsFake(i => { return inputs[i] || null }), 18 | getVariable: sinon.stub().callsFake(v => { return variables[v] || null }), 19 | getEndpointAuthorizationParameter: () => 'fooPAT', 20 | loc: sinon.stub().returnsArg(0) 21 | }) 22 | 23 | rewiremock.enable() 24 | 25 | import { IClientFactory, CreatePRCommentTask } from '../src/task' 26 | 27 | class ClientFactoryMock implements IClientFactory { 28 | called: boolean = false 29 | createdCommentThread: GitInterfaces.GitPullRequestCommentThread = {} 30 | createdRepositoryId: string = '' 31 | createdPullRequestId: number = 0 32 | expectedThreads: GitInterfaces.GitPullRequestCommentThread[] = [] 33 | 34 | public async create (): Promise { 35 | const gitApiStub = { 36 | getThreads: async (repositoryId: string, pullRequestId: number, project?: string, iteration?: number, baseIteration?: number): Promise => { 37 | return await new Promise( 38 | (resolve: (value: GitInterfaces.GitPullRequestCommentThread[]) => void, reject: (reason?: any) => void) => { 39 | resolve(this.expectedThreads) 40 | }) 41 | }, 42 | createThread: async (commentThread: GitInterfaces.GitPullRequestCommentThread, repositoryId: string, pullRequestId: number, project?: string): Promise => { 43 | return await new Promise( 44 | (resolve: (value: GitInterfaces.GitPullRequestCommentThread) => void, reject: (reason?: any) => void) => { 45 | this.createdCommentThread = commentThread 46 | this.createdRepositoryId = repositoryId 47 | this.createdPullRequestId = pullRequestId 48 | this.called = true 49 | resolve(undefined!) // currently not used. 50 | }) 51 | } 52 | } 53 | return gitApiStub 54 | } 55 | } 56 | 57 | describe('CreatePRCommentTaskV0 Tests', function () { 58 | it('run all inputs function', async () => { 59 | const factoryMock: IClientFactory = new ClientFactoryMock() 60 | variables['Build.Repository.ID'] = '3' 61 | variables['System.PullRequest.PullRequestId'] = '4' 62 | inputs.AzureDevOpsService = 'devopspat' 63 | inputs.Comment = 'foo' 64 | 65 | const commentTask = new CreatePRCommentTask(factoryMock) 66 | await commentTask.run(); 67 | 68 | (factoryMock as ClientFactoryMock).called.should.be.true 69 | }) 70 | 71 | it('run substitution', async () => { 72 | const factoryMock: IClientFactory = new ClientFactoryMock() 73 | variables['Build.Repository.ID'] = '3' 74 | variables['System.PullRequest.PullRequestId'] = '4' 75 | variables.Bar = 'bar' 76 | inputs.AzureDevOpsService = 'devopspat' 77 | inputs.Comment = 'foo, $(Bar)' 78 | 79 | const commentTask = new CreatePRCommentTask(factoryMock) 80 | await commentTask.run() 81 | const comments = (factoryMock as ClientFactoryMock).createdCommentThread.comments 82 | if (comments !== undefined) { 83 | const content = comments[0].content 84 | if (content !== undefined) { 85 | content.should.be.equal('foo, bar') 86 | } else { 87 | assert.fail('content is undefined') 88 | } 89 | } else { 90 | assert.fail('comments is undefined') 91 | } 92 | }) 93 | 94 | it('ignored if it is non-pullrequest pipeline', async () => { 95 | const factoryMock: IClientFactory = new ClientFactoryMock() 96 | variables = {} 97 | variables['Build.Repository.ID'] = '3' 98 | inputs.AzureDevOpsService = 'devopspat' 99 | inputs.Comment = 'foo' 100 | 101 | const commentTask = new CreatePRCommentTask(factoryMock) 102 | await commentTask.run(); 103 | 104 | (factoryMock as ClientFactoryMock).called.should.be.false 105 | }) 106 | 107 | it('suppress the comment if there is already created', async () => { 108 | const factoryMock: IClientFactory = new ClientFactoryMock() 109 | variables['Build.Repository.ID'] = '3' 110 | variables['System.PullRequest.PullRequestId'] = '4' 111 | inputs.AzureDevOpsService = 'devopspat' 112 | inputs.Comment = 'foo' 113 | const commentObject = { 114 | content: 'foo' 115 | } 116 | const thread: GitInterfaces.GitPullRequestCommentThread = { 117 | comments: [ 118 | commentObject 119 | ] 120 | }; 121 | (factoryMock as ClientFactoryMock).expectedThreads = [thread] 122 | 123 | const commentTask = new CreatePRCommentTask(factoryMock) 124 | await commentTask.run(); 125 | 126 | (factoryMock as ClientFactoryMock).called.should.be.false 127 | }) 128 | }) 129 | 130 | rewiremock.disable() 131 | -------------------------------------------------------------------------------- /CreatePrComment/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | "sourceMap": false, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": ".bin/", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | /* Module Resolution Options */ 39 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 49 | /* Source Map Options */ 50 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | /* Experimental Options */ 55 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 56 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 57 | "skipLibCheck": true, 58 | "forceConsistentCasingInFileNames": true 59 | } 60 | } -------------------------------------------------------------------------------- /CreatePrComment/vss-extension-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/CreatePrComment/vss-extension-icon.png -------------------------------------------------------------------------------- /CreatePrComment/vss-extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifestVersion": 1, 3 | "id": "create-pr-comment-task", 4 | "name": "Create Pull Request Comment", 5 | "publisher": "CSE-DevOps", 6 | "version": "0.1.0", 7 | "galleryFlags": [ 8 | "Public", 9 | "Preview" 10 | ], 11 | "targets": [ 12 | { 13 | "id": "Microsoft.VisualStudio.Services" 14 | } 15 | ], 16 | "description": "Create Pull Request Comment", 17 | "categories": [ 18 | "Azure Pipelines" 19 | ], 20 | "tags": [ 21 | "Pull Request", 22 | "PR", 23 | "PR Comment", 24 | "Utility task", 25 | "Utility", 26 | "Azure Pipelines" 27 | ], 28 | "content": { 29 | "details": { 30 | "path": "README.md" 31 | }, 32 | "license": { 33 | "path": "LICENSE.md" 34 | } 35 | }, 36 | "icons": { 37 | "default": "vss-extension-icon.png" 38 | }, 39 | "files": [ 40 | { 41 | "path": "task" 42 | } 43 | ], 44 | "links": { 45 | "home": { 46 | "uri": "https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment" 47 | }, 48 | "getstarted": { 49 | "uri": "https://github.com/microsoft/CSEDevOps/blob/main/CreatePrComment/README.md" 50 | }, 51 | "learn": { 52 | "uri": "https://github.com/microsoft/CSEDevOps/blob/main/CreatePrComment/README.md" 53 | }, 54 | "support": { 55 | "uri": "https://github.com/microsoft/CSEDevOps/discussions/categories/createprcomment" 56 | }, 57 | "repository": { 58 | "uri": "https://github.com/microsoft/CSEDevOps/tree/main/CreatePrComment" 59 | }, 60 | "issues": { 61 | "uri": "https://github.com/microsoft/CSEDevOps/issues?q=is:issue+label:CreatePrComment" 62 | }, 63 | "license": { 64 | "uri": "https://github.com/microsoft/CSEDevOps/blob/main/CreatePrComment/LICENSE.md" 65 | } 66 | }, 67 | "repository": { 68 | "type": "git", 69 | "uri": "https://github.com/microsoft/CSEDevOps" 70 | }, 71 | "contributions": [ 72 | { 73 | "id": "service-endpoint", 74 | "description": "Service Endpoint type for Azure DevOps PAT", 75 | "type": "ms.vss-endpoint.service-endpoint-type", 76 | "targets": [ 77 | "ms.vss-endpoint.endpoint-types" 78 | ], 79 | "properties": { 80 | "name": "azuredevops", 81 | "displayName": "CreatePRCommentTaskV0 Azure DevOps PAT", 82 | "config": { 83 | "type": "string", 84 | "description": "Put your Personal Access Token of the Azure DevOps", 85 | "required": true 86 | }, 87 | "authenticationSchemes": [ 88 | { 89 | "type": "ms.vss-endpoint.endpoint-auth-scheme-none", 90 | "inputDescriptors": [ 91 | { 92 | "id": "pat", 93 | "name": "PAT", 94 | "description": "Azure DevOps Personal Access Token here.", 95 | "inputMode": "passwordbox", 96 | "isConfidential": true, 97 | "validation": { 98 | "isRequired": true, 99 | "dataType": "string" 100 | } 101 | } 102 | ] 103 | } 104 | ], 105 | "helpMarkDown": "Create a Pull Request Comment." 106 | } 107 | }, 108 | { 109 | "id": "custom-build-release-task", 110 | "type": "ms.vss-distributed-task.task", 111 | "targets": [ 112 | "ms.vss-distributed-task.tasks" 113 | ], 114 | "properties": { 115 | "name": "task" 116 | } 117 | } 118 | ] 119 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /PipelineRunnerExtension/README.MD: -------------------------------------------------------------------------------- 1 | # Configurable Pipeline Runner 2 | 3 | The purpose of this Build task is to provide a solution for : 4 | 5 | - Triggering Remote/Delegate/Child Pipelines any amount of times from a Parent pipeline. 6 | - The build task allows passing variables that are passed to the delegate pipeline at runtime. 7 | - The variable array in Json Array format will determine how many delegate pipeline instances are started (Restricted to the Pipeline Agents Available) 8 | - Monitor the execution of the remotely triggered delegate pipelines by polling execution status on a configurable interval before completing. 9 | - Downloads any Artifacts Produced by the remotely triggered delegate pipelines and publishes it to the Parent Pipeline. 10 | - Creates a summary page with links to the pipeline execution results for easy reference. 11 | 12 | ## Getting Started 13 | 14 | The Build Task requires the configuration of an Azure DevOps service connection. The respective service connection will need personal access token with multiple organization access. You can read more about creating your [Personal Access Token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=vsts) here, 15 | 16 | Why is a PAT required to configure the task ? 17 | Answer: The PAT is used at design time to query the Azure DevOps REST APIs for the configure Service Connection in order to return filtered lists containing relevant Projects,Folders (in which build definitions are nested) and the Build Definitions. 18 | 19 | At runtime the Build Task uses the Azure Pipelines CLI to trigger and monitor pipelines and uses the SystemAccessToken for authentication. 20 | 21 | 1. Create a Service Connection 22 | 23 | ![Create/Configure Service Connection](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/PipelineRunnerExtension/images/screenshot-2.png) 24 | 25 | 2. Configure Build Task Properties/Fields 26 | 27 | Configure the task with the required properties to trigger your builds. The Pipeline task can be added to you Parent Pipeline multiple times but will require that you set a unique CustomPrefix per instance in order to correctly separate build artifacts and summary reports as depicted in the following numbered list. 28 | 29 | ![Configure Pipeline Properties](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/PipelineRunnerExtension/images/screenshot-1.png) 30 | 31 | 3. Review Pipeline Summary Report 32 | 33 | For each instance of the Build Task that is configured a Summary Report will be displayed using the CustomPrefix provided during the task configuration. It will list the builds that it executed as well as it's final status, either **Succeeded/Failed** with a clickable link to the build results page for easy referencing. 34 | 35 | ![Review Report](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/PipelineRunnerExtension/images/screenshot-3.png) 36 | 37 | 4. Review Artifacts 38 | 39 | If the triggered builds produce artifacts and you have configured the task to download artifacts the artifacts will be consolidated in a single published artifact, organized by BuildID of the delegate builds. If the task is added to your Parent Pipeline multiple times, the artifacts will be published using the unique CustomPrefix that you have configured per instance as a grouping mechanism. 40 | 41 | ![Review Artifacts](https://raw.githubusercontent.com/microsoft/CSEDevOps/main/PipelineRunnerExtension/images/screenshot-4.png) 42 | 43 | ## Task Fields 44 | 45 | This section describes the fields that can be configured on the Build Task 46 | 47 | Inputs: 48 | 49 | - **Service Connection**: Pick List which lists service connections created as in **step 1** above 50 | - **Project**: Project in which the Build Definitions can be found 51 | - **Folder Path**: Folder location where Build Definitions are stored as an organizational unit 52 | - **Build Definition**: The Name of the Build that you wish to trigger 53 | - **Set Branch**: Boolean property which enables a field that allows you to pick define a branch against which the triggered pipeline must run. If not selected the pipeline will be run against the main branch 54 | - **Branch**: Set the branch that the triggered pipeline must target. If left blank defaults to main 55 | - **Parameter Source**: 'inline'/ 'filepath' allows build parameters to be passed from a file or inline 56 | - **Build Variables**: Sample Json Array '[{"Var1":"hello","Var2":"world" },{"Var1":"Bye","Var2":"world" }]' in the preceding example the delegate pipeline will be executed twice one for each **element {}** and in each case the variables **Var1** and **Var2** will be passed to the Pipeline being executed as variables 57 | - **Custom Prefix**: Must be set to a unique string value if the Build Task is added to Parent Pipeline more than once 58 | - **Wait Till Triggered Builds Finish**: Boolean value to determine weather a fan-out and fan in pattern is observed. If left unchecked delegate pipelines will be Triggered, but the parent pipeline will not wait for completion and will not collect artifacts or report status of the resultant builds. 59 | - **Build Completion Polling Interval**: The sleep time between status checks set in seconds 60 | - **Build Timeout in minutes**: Time to wait before abandoning polling of queued builds. 61 | - **Download Build Artifacts**: Boolean field to opt into downloading build Artifacts 62 | - **Artifact Drop Directory**: Defaults to '$(Build.ArtifactStagingDirectory)' and is the directory where build artifacts will be stored temporarily 63 | - **Store Triggered Build Ids In A Variable**: boolean value to determine whether an environment variable with the following naming convention ${CUSTOMPREFIX}_BUILD_IDS or TRIGGERED_BUILD_IDS where a CustomPrefix has not been provided and there is only a single instance of the build task in the Parent pipeline. 64 | -------------------------------------------------------------------------------- /PipelineRunnerExtension/images/connection-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/PipelineRunnerExtension/images/connection-icon.png -------------------------------------------------------------------------------- /PipelineRunnerExtension/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/PipelineRunnerExtension/images/icon.png -------------------------------------------------------------------------------- /PipelineRunnerExtension/images/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/PipelineRunnerExtension/images/screenshot-1.png -------------------------------------------------------------------------------- /PipelineRunnerExtension/images/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/PipelineRunnerExtension/images/screenshot-2.png -------------------------------------------------------------------------------- /PipelineRunnerExtension/images/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/PipelineRunnerExtension/images/screenshot-3.png -------------------------------------------------------------------------------- /PipelineRunnerExtension/images/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/PipelineRunnerExtension/images/screenshot-4.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSE DevOps Extensions 2 | 3 | ## Available Extensions 4 | 5 | - [Create Pull Request Comment](./CreatePrComment) 6 | - [OWASP ZAP Scanner](./Zap) 7 | - [Configurable Pipeline Runner](./PipelineRunnerExtension) 8 | 9 | ## Getting Help 10 | 11 | Please see the guidance provided in the documentation for each perspective extension linked above as well as the Marketplace. Please submit issues with a descriptions of problems you encounter while using these extension. 12 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [many more](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [definition]() of a security vulnerability, please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | - Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | - Full paths of source file(s) related to the manifestation of the issue 23 | - The location of the affected source code (tag/branch/commit or direct URL) 24 | - Any special configuration required to reproduce the issue 25 | - Step-by-step instructions to reproduce the issue 26 | - Proof-of-concept or exploit code (if possible) 27 | - Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /Zap/README.md: -------------------------------------------------------------------------------- 1 | # OWASP/ZAP Scanning extension for Azure DevOps 2 | 3 | ![Build Status](https://csedevops.visualstudio.com/DevSecOps/_apis/build/status/owasp-zap-azure-extension) 4 | 5 | [OWASP/ZAP](https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project) is a popular free security tool for helping to identify vulnerabilities during the development process from [OWASP](https://www.owasp.org). This extension shifts scanning and reporting into the Azure DevOps Pipeline model to enable quick feedback and response from development teams throughout the development life-cycle. 6 | 7 | ![azure task](https://raw.githubusercontent.com/microsoft/CSEDevOps/master/Zap/docs/images/zap-scanner-task.png) 8 | 9 | ## Usage 10 | 11 | ### Prerequisite 12 | 13 | This task simplifies shifting security scanning of web applications into the DevOps pipeline in part by removing the requirement of having a running, exposed ZAP proxy before attempting the scan. By installing the proxy, you are enabling self-contained scans within your CI/CD pipeline. The core requirement for usage is a Docker install available to this task which supports running Linux containers. The operating system of the host is otherwise irrelevant. When this requirement is not met, the task will fail with an error message of `image operating system "linux" cannot be used on this platform`. 14 | 15 | ### Configuration 16 | 17 | After installing the scanner from the Azure DevOps Marketplace, you will need to add the scanner to your agent job and configure a few basic requirements. 18 | 19 | ![zap scanner task config](https://raw.githubusercontent.com/microsoft/CSEDevOps/master/Zap/docs/images/zap-scanner-config.png) 20 | 21 | - The "Display name" of the task can be left as-is, or it can be updated to fit withing the naming conventions of your pipeline better. 22 | 23 | - By default, the task will run a baseline scan. 24 | 25 | - Custom configuration for ZAP may be provided through a "context file". This is ZAP functionality, and [is documented by the official project team](https://www.zaproxy.org/docs/desktop/start/features/contexts/). 26 | 27 | #### Baseline Scan Notes 28 | 29 | > The baseline scan will spider the target for 1 minute and then wait for the passive scanning to complete. This makes for a relatively short-running scan that doesn't perform any attacks. 30 | 31 | #### Full Scan Notes 32 | 33 | > A full-scan can be run by ticking the "Aggressive Scan Mode" checkbox. This scan doesn't have a time limit and does perform 'attacks.' It can run for a long time. Not ideally suited for CI, but is a useful tool for release-gates. ![aggressive-scan](https://raw.githubusercontent.com/microsoft/CSEDevOps/master/Zap/docs/images/zap-aggressive.png) 34 | 35 | - The "Failure Threshold" indicates the score at which the pipeline will begin to fail. The scoring mechanism built into this scanner is meant to be suggestive, and security personnel knowledgable about threat-models for the specific application should be engaged to adjust this value appropriately. The default of 50 is only a starting point for incorporating the scanner and is not likely to be what works for your application! The ZAP scanner generates an HTML report (available in the Az DevOps build artifacts) that contains one entry per vulnerability discovered along with a confidence rating without any grouping. This approach does not provide a useful story from a developer perspective. The threshold sets out to provide a handy reference for improving the development workflow when considering vulnerability scanning. This limit is an aggregate score that first groups like vulnerabilities before applying the confidences, and total times a type of vulnerability appears to score the scan. 36 | 37 | - By default, that "Scan Type" used is "Scan on Agent." This type of scan is beneficial in pipelines for containerized applications. **This usage requires a preceding step to build your image and run it (detached) within the agent.** 38 | 39 | - When running a full-scan in release-pipelines, or if your application is not containerized and has to run in a VM, "Scan Type" can be changed to "Targeted Scan." In this case, you are required to provide the schema and address for your target. Ex. or . 40 | 41 | > - By default, the scan will be performed without much effort at scoping for tech, excluded URL endpoints, etc.., but you can provide a context file for a more focused scan. The context file is useful to take full advantage of the baseline scans minute of crawling or, to narrow the scope of a full-scan in aggressive mode to keep the duration as short as possible. To accomplish this, check the "Provide Context File" box and provide the path to a context file in your source repository relative to the build copy of the source. ![context provided](https://raw.githubusercontent.com/microsoft/CSEDevOps/master/Zap/docs/images/zap-context-provided.png) 42 | 43 | - ZAP contexts and context files are not within the scope of our documentation. [Please refer to ZAP's official documentation, here.](https://www.zaproxy.org/docs/desktop/start/features/contexts/) 44 | 45 | - Finally, provide an optional port number for custom ports. By default, the scan will be interested in port 80 on the target system. 46 | 47 | ### Reporting Options 48 | 49 | The extension generates 2 reports (report.html & report.json) that both contain the details of the results. These reports are in a directory "owaspzap" once the scanner completes. To be useful the report can be staged and published. For example the following tasks will attach these raw output files as build artifacts. 50 | 51 | The ZAP scanner includes several reporting options. None of which are optimal for use by development teams. However, we do provide several of these (HTML & JSON) as downloadable artifacts in the build. To make the scans more useful for development teams and get real feedback into the pipeline, we want a more helpful report of the scan results. Above, we discussed generating a threshold for scoring and failing a CI build. Below are some path(s) to reporting. The goal here is providing a flexible extension that can be reported on as appropriate for your project/team with some possible paths on getting even more value from the extensions. 52 | 53 | #### Publish Reports as Build Artifacts 54 | 55 | If you would like to publish the generated html & json reports as build artifacts you can use the copy and publish tasks available in Azure Devops! 56 | 57 | ``` bash 58 | - task: CopyFiles@2 59 | condition: always() 60 | inputs: 61 | SourceFolder: 'owaspzap/' 62 | TargetFolder: '$(Build.ArtifactStagingDirectory)' 63 | 64 | - task: PublishBuildArtifacts@1 65 | condition: always() 66 | inputs: 67 | ArtifactName: 'owasp_zap_reports' 68 | ``` 69 | 70 | #### Install handlebars 71 | 72 | Additionally, you can take advantage of handlebars templating for a simple reporting dashboard/tab and generate the template to report from. 73 | 74 | ```YAML 75 | - bash: | 76 | sudo npm install -g handlebars-cmd 77 | 78 | cat < owaspzap/nunit-template.hbs 79 | 80 | 84 | {{#each site}} 90 | 91 | 92 | $BUILD_SOURCESDIRECTORY/owaspzap/report.html 93 | 94 | 95 | {{#each alerts}} 101 | 102 | 103 | 104 | 105 | 106 | 118 | 119 | 120 | 121 | {{/each}} 122 | 123 | {{/each}} 124 | 125 | EOF 126 | displayName: 'owasp nunit template' 127 | condition: always() 128 | ``` 129 | 130 | #### Report Generation 131 | 132 | Use the handlebars template and json report from the ZAP scan to generate an xml report in the nunit style to display. 133 | 134 | ```YAML 135 | - bash: ' handlebars owaspzap/report.json < owaspzap/nunit-template.hbs > owaspzap/test-results.xml' 136 | displayName: 'generate nunit type file' 137 | condition: always() 138 | ``` 139 | 140 | #### Publish Report (Nunit Style) 141 | 142 | ```YAML 143 | - task: PublishTestResults@2 144 | displayName: 'Publish Test Results **/TEST-*.xml' 145 | inputs: 146 | testResultsFormat: NUnit 147 | testResultsFiles: 'owaspzap/test-results.xml' 148 | condition: always() 149 | ``` 150 | 151 | You should now have 'test' tab on the pipeline build that displays useful infomration about vulnerabilities revealed during the scan. The vulnerability test results are not reliant on the pass/fail outcome of the build. This means that even with a scan that is acceptable by the threshold you could have some areas of concern to explore in the repot. 152 | 153 | ![full report view](https://raw.githubusercontent.com/microsoft/CSEDevOps/master/Zap/docs/images/scan-results-collapsed.png) 154 | 155 | The list of failures (currently under "NUnit Test Run) are expandable links. Clicking on each will open details to the right which can guide the team to fixing the issues found. 156 | 157 | ![vulnerability drill-down](https://raw.githubusercontent.com/microsoft/CSEDevOps/master/Zap/docs/images/scan-results-drill-down.png) 158 | 159 | #### Other Reporting Options 160 | 161 | Other reporting options will be added to the extension. The HTML & JSON report will remain and offer the flexibility for parsing and publishing customer reports as above using the approach that works best for your team and project. 162 | -------------------------------------------------------------------------------- /Zap/docs/images/scan-results-collapsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/Zap/docs/images/scan-results-collapsed.png -------------------------------------------------------------------------------- /Zap/docs/images/scan-results-drill-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/Zap/docs/images/scan-results-drill-down.png -------------------------------------------------------------------------------- /Zap/docs/images/zap-aggressive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/Zap/docs/images/zap-aggressive.png -------------------------------------------------------------------------------- /Zap/docs/images/zap-context-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/Zap/docs/images/zap-context-empty.png -------------------------------------------------------------------------------- /Zap/docs/images/zap-context-provided.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/Zap/docs/images/zap-context-provided.png -------------------------------------------------------------------------------- /Zap/docs/images/zap-scanner-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/Zap/docs/images/zap-scanner-config.png -------------------------------------------------------------------------------- /Zap/docs/images/zap-scanner-task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/Zap/docs/images/zap-scanner-task.png -------------------------------------------------------------------------------- /Zap/docs/images/zap-target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/CSEDevOps/b25c6b6e45e40bea9204ac3a9cfdebecc8dab4c0/Zap/docs/images/zap-target.png --------------------------------------------------------------------------------