├── .nycrc ├── CODE_OF_CONDUCT.md ├── package.json ├── LICENSE ├── action.yml ├── .github └── workflows │ └── ci.yml ├── SECURITY.md ├── src ├── Tests │ └── taskparameters.test.ts ├── main.ts └── taskparameters.ts ├── lib ├── taskparameters.js └── main.js ├── tsconfig.json ├── README.md └── .gitignore /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "cache": false, 3 | "check-coverage": false, 4 | "extension": [ 5 | ".ts" 6 | ], 7 | "include": [ 8 | "**/*.js", 9 | "**/*.ts" 10 | ], 11 | "exclude": [ 12 | "coverage/**", 13 | "node_modules/**", 14 | "**/*.d.ts", 15 | "**/*.test.ts", 16 | "**/*.js" 17 | ], 18 | "sourceMap": true, 19 | "reporter": [ 20 | "html", 21 | "text", 22 | "text-summary" 23 | ], 24 | "all": true, 25 | "instrument": true 26 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webapps-container-deploy", 3 | "version": "1.0.0", 4 | "description": "Deploy to webapp container", 5 | "main": "lib/main.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "test": "mocha -r ts-node/register src/**/*.test.ts", 9 | "coverage": "nyc npm run test" 10 | }, 11 | "author": "Sumiran Aggarwal", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@types/chai": "^4.2.11", 15 | "@types/mocha": "^7.0.2", 16 | "@types/node": "^12.6.8", 17 | "@types/sinon": "^7.5.2", 18 | "chai": "^4.2.0", 19 | "mocha": "^7.1.1", 20 | "nyc": "^15.0.0", 21 | "sinon": "^9.0.1", 22 | "ts-node": "^8.8.1", 23 | "typescript": "^3.5.3" 24 | }, 25 | "dependencies": { 26 | "@actions/core": "^1.0.0", 27 | "@types/q": "^1.5.2", 28 | "azure-actions-appservice-rest": "^1.0.7", 29 | "azure-actions-webclient": "^1.0.8" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | # This action is ARCHIVED, update your workflows to use the new action azure/webapps-deploy@v2 which can deploy to both Webapps and Webapp for containers. 2 | name: 'Azure WebApp Container' 3 | description: 'Deploy Container Web Apps to Azure. github.com/Azure/Actions' 4 | inputs: 5 | app-name: 6 | description: 'Name of the Azure Web App' 7 | required: true 8 | deprecationMessage: 'This action is ARCHIVED and will not receive any updates, update your workflows to use azure/webapps-deploy@v2' 9 | slot-name: 10 | description: 'Enter an existing Slot other than the Production slot' 11 | required: false 12 | default: 'production' 13 | images: 14 | description: "Specify the fully qualified container image(s) name. For example, 'myregistry.azurecr.io/nginx:latest' or 'python:3.7.2-alpine/'. For multi-container scenario multiple container image names can be provided (multi-line separated)" 15 | required: true 16 | configuration-file: 17 | description: 'Path of the Docker-Compose file. Should be a fully qualified path or relative to the default working directory. Required for multi-container scenario' 18 | required: false 19 | container-command: 20 | description: 'Enter the start up command. For ex. dotnet run or dotnet filename.dll' 21 | required: false 22 | outputs: 23 | webapp-url: 24 | description: 'URL to work with your webapp' 25 | branding: 26 | icon: 'container-webapp.svg' 27 | color: 'blue' 28 | runs: 29 | using: 'node12' 30 | main: 'lib/main.js' 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | branches: 4 | - master 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build_test_job: 11 | name: 'Build and test job' 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [windows-latest, ubuntu-latest, macos-latest] 16 | steps: 17 | 18 | - name: 'Checking out repo code' 19 | uses: actions/checkout@v2 20 | 21 | - name: 'Validate build' 22 | run: | 23 | npm install 24 | npm run build 25 | 26 | - name: 'Run L0 tests' 27 | run: | 28 | npm run test 29 | 30 | - name : 'Run test coverage' 31 | if: runner.os == 'Windows' 32 | env: 33 | PR_NUMBER: ${{ github.event.pull_request.number }} 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | run: | 36 | $coverage_result = npm run coverage 37 | $start = $false; 38 | $middle = $false; 39 | $end = $false; 40 | $count = 0; 41 | 42 | foreach ($j in $coverage_result) 43 | { 44 | if ($j.tostring().startswith("--------------")) 45 | { 46 | if (!$start) 47 | { 48 | $start = $true; 49 | $start_index = $count 50 | } 51 | elseif (!$middle) 52 | { 53 | $middle = $true; 54 | $middle_index = $count 55 | } 56 | elseif (!$end) 57 | { 58 | $end = $true; 59 | $end_index = $count 60 | } 61 | } 62 | $count++ 63 | } 64 | 65 | $tbl_md = $coverage_result[($start_index+1)..($end_index-1)] -join "\n" 66 | $summary = $coverage_result[($end_index + 1)..$count] -join "\n" 67 | $comment = $tbl_md + "\n" + $summary 68 | 69 | $url = "https://api.github.com/repos/${env:GITHUB_REPOSITORY}/issues/${env:PR_NUMBER}/comments" 70 | $headers = @{ 71 | "Authorization" = "token ${env:GITHUB_TOKEN}" 72 | } 73 | Invoke-RestMethod -Method POST -Uri $url -Headers $headers -Body "{ `"body`": `"${comment}`" }" -------------------------------------------------------------------------------- /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](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) 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.** Instead, please report them to the Microsoft Security Response Center at [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://technet.microsoft.com/en-us/security/dn606155). 12 | 13 | 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). 14 | 15 | 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: 16 | 17 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 18 | * Full paths of source file(s) related to the manifestation of the issue 19 | * The location of the affected source code (tag/branch/commit or direct URL) 20 | * Any special configuration required to reproduce the issue 21 | * Step-by-step instructions to reproduce the issue 22 | * Proof-of-concept or exploit code (if possible) 23 | * Impact of the issue, including how an attacker might exploit the issue 24 | 25 | This information will help us triage your report more quickly. 26 | 27 | ## Preferred Languages 28 | 29 | We prefer all communications to be in English. 30 | 31 | ## Policy 32 | 33 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 34 | 35 | -------------------------------------------------------------------------------- /src/Tests/taskparameters.test.ts: -------------------------------------------------------------------------------- 1 | import { exist, TaskParameters } from "../taskparameters"; 2 | import { AzureResourceFilterUtility } from "azure-actions-appservice-rest/Utilities/AzureResourceFilterUtility"; 3 | import { IAuthorizer } from "azure-actions-webclient/Authorizer/IAuthorizer"; 4 | 5 | import chai = require('chai'); 6 | import sinon = require("sinon"); 7 | 8 | var expect = chai.expect; 9 | 10 | describe('Test task parameter functions', () => { 11 | 12 | beforeEach(function() { 13 | sinon.stub(AzureResourceFilterUtility, 'getAppDetails'); 14 | }); 15 | 16 | afterEach(function() { 17 | (AzureResourceFilterUtility.getAppDetails as sinon.SinonStub).restore(); 18 | }); 19 | 20 | it("Test app-name is required input", () => { 21 | let mockEndpoint: IAuthorizer; 22 | expect(() => TaskParameters.getTaskParams(mockEndpoint)).to.throw(); 23 | }); 24 | 25 | it("Test taskparamters for multi container with no config file", () => { 26 | process.env['INPUT_APP-NAME'] = 'testAppName'; 27 | process.env['INPUT_IMAGES'] = 'testImage\ntestImage2'; 28 | 29 | let mockEndpoint: IAuthorizer; 30 | expect(() => TaskParameters.getTaskParams(mockEndpoint)).to.throw(Error, "Multiple images indicate multi-container deployment type, but Docker-compose file is absent."); 31 | }); 32 | 33 | it("Test taskparamters for single container", async () => { 34 | let expectedAppDetails = { 35 | "resourceGroupName" : "MockResourceGroupName", 36 | "kind" : "MockKind" 37 | }; 38 | (AzureResourceFilterUtility.getAppDetails as sinon.SinonStub).resolves(expectedAppDetails); 39 | 40 | process.env['INPUT_APP-NAME'] = 'testAppName'; 41 | process.env['INPUT_IMAGES'] = 'testImage'; 42 | 43 | let mockEndpoint: IAuthorizer; 44 | let taskparams: TaskParameters = TaskParameters.getTaskParams(mockEndpoint); 45 | 46 | expect(taskparams.appName).to.equal('testAppName'); 47 | expect(taskparams.images).to.equal('testImage'); 48 | expect(taskparams.isMultiContainer).to.be.false; 49 | 50 | await taskparams.getResourceDetails(); 51 | 52 | expect(taskparams.resourceGroupName).to.equal('MockResourceGroupName'); 53 | expect(taskparams.isLinux).to.be.false; 54 | }); 55 | 56 | it("Test exist function works correctly", () => { 57 | let correctPathExists = exist("src"); 58 | expect(correctPathExists).to.be.true; 59 | 60 | let wrongPathExists = exist("wrongPath"); 61 | expect(wrongPathExists).to.be.false; 62 | }); 63 | }); -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | import * as crypto from "crypto"; 3 | 4 | import { AuthorizerFactory } from "azure-actions-webclient/AuthorizerFactory"; 5 | import { AzureAppService } from 'azure-actions-appservice-rest/Arm/azure-app-service'; 6 | import { AzureAppServiceUtility } from 'azure-actions-appservice-rest/Utilities/AzureAppServiceUtility'; 7 | import { ContainerDeploymentUtility } from 'azure-actions-appservice-rest/Utilities/ContainerDeploymentUtility'; 8 | import { IAuthorizer } from 'azure-actions-webclient/Authorizer/IAuthorizer'; 9 | import { KuduServiceUtility } from 'azure-actions-appservice-rest/Utilities/KuduServiceUtility'; 10 | import { TaskParameters } from './taskparameters'; 11 | import { addAnnotation } from 'azure-actions-appservice-rest/Utilities/AnnotationUtility'; 12 | 13 | var prefix = !!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT}` : ""; 14 | 15 | async function main() { 16 | let isDeploymentSuccess: boolean = true; 17 | 18 | try { 19 | // Set user agent varable 20 | let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex'); 21 | let actionName = 'DeployWebAppToAzure'; 22 | let userAgentString = (!!prefix ? `${prefix}+` : '') + `GITHUBACTIONS_${actionName}_${usrAgentRepo}`; 23 | core.exportVariable('AZURE_HTTP_USER_AGENT', userAgentString); 24 | 25 | let endpoint: IAuthorizer = await AuthorizerFactory.getAuthorizer(); 26 | var taskParams = TaskParameters.getTaskParams(endpoint); 27 | await taskParams.getResourceDetails(); 28 | 29 | core.debug("Predeployment Step Started"); 30 | var appService = new AzureAppService(taskParams.endpoint, taskParams.resourceGroupName, taskParams.appName, taskParams.slotName); 31 | var appServiceUtility = new AzureAppServiceUtility(appService); 32 | 33 | var kuduService = await appServiceUtility.getKuduService(); 34 | var kuduServiceUtility = new KuduServiceUtility(kuduService); 35 | 36 | core.debug("Deployment Step Started"); 37 | core.debug("Performing container based deployment."); 38 | 39 | let containerDeploymentUtility: ContainerDeploymentUtility = new ContainerDeploymentUtility(appService); 40 | await containerDeploymentUtility.deployWebAppImage(taskParams.images, taskParams.multiContainerConfigFile, taskParams.isLinux, taskParams.isMultiContainer, taskParams.containerCommand); 41 | } 42 | catch (error) { 43 | core.debug("Deployment Failed with Error: " + error); 44 | isDeploymentSuccess = false; 45 | core.setFailed(error); 46 | } 47 | finally { 48 | if(!!kuduServiceUtility) { 49 | await addAnnotation(taskParams.endpoint, appService, isDeploymentSuccess); 50 | let activeDeploymentID = await kuduServiceUtility.updateDeploymentStatus(isDeploymentSuccess, null, {'type': 'Deployment', slotName: appService.getSlot()}); 51 | core.debug('Active DeploymentId :'+ activeDeploymentID); 52 | } 53 | 54 | let appServiceApplicationUrl: string = await appServiceUtility.getApplicationURL(); 55 | console.log('App Service Application URL: ' + appServiceApplicationUrl); 56 | core.setOutput('webapp-url', appServiceApplicationUrl); 57 | 58 | // Reset AZURE_HTTP_USER_AGENT 59 | core.exportVariable('AZURE_HTTP_USER_AGENT', prefix); 60 | core.debug(isDeploymentSuccess ? "Deployment Succeded" : "Deployment failed"); 61 | } 62 | } 63 | 64 | main(); -------------------------------------------------------------------------------- /src/taskparameters.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core'; 2 | 3 | import { AzureResourceFilterUtility } from "azure-actions-appservice-rest/Utilities/AzureResourceFilterUtility"; 4 | import { IAuthorizer } from "azure-actions-webclient/Authorizer/IAuthorizer"; 5 | 6 | import fs = require('fs'); 7 | 8 | export class TaskParameters { 9 | private static taskparams: TaskParameters; 10 | private _appName: string; 11 | private _images: string; 12 | private _resourceGroupName?: string; 13 | private _multiContainerConfigFile?: string; 14 | private _endpoint: IAuthorizer; 15 | private _containerCommand: string; 16 | private _kind: string; 17 | private _isLinux: boolean; 18 | private _isMultiContainer: boolean; 19 | private _slotName: string; 20 | 21 | private constructor(endpoint: IAuthorizer) { 22 | this._appName = core.getInput('app-name', { required: true }); 23 | this._slotName = core.getInput('slot-name'); 24 | this._images = core.getInput('images'); 25 | this._multiContainerConfigFile = core.getInput('configuration-file'); 26 | this._containerCommand = core.getInput('container-command'); 27 | this._endpoint = endpoint; 28 | this.checkContainerType(); 29 | } 30 | 31 | public static getTaskParams(endpoint: IAuthorizer) { 32 | if(!this.taskparams) { 33 | this.taskparams = new TaskParameters(endpoint); 34 | } 35 | return this.taskparams; 36 | } 37 | 38 | public get appName() { 39 | return this._appName; 40 | } 41 | 42 | public get slotName(){ 43 | return this._slotName; 44 | } 45 | 46 | public get images() { 47 | return this._images; 48 | } 49 | 50 | public get resourceGroupName() { 51 | return this._resourceGroupName; 52 | } 53 | 54 | public get endpoint() { 55 | return this._endpoint; 56 | } 57 | 58 | public get isLinux() { 59 | return this._isLinux; 60 | } 61 | 62 | public get isMultiContainer() { 63 | return this._isMultiContainer; 64 | } 65 | 66 | public get containerCommand() { 67 | return this._containerCommand; 68 | } 69 | 70 | public get multiContainerConfigFile() { 71 | return this._multiContainerConfigFile; 72 | } 73 | 74 | public async getResourceDetails() { 75 | let appDetails = await AzureResourceFilterUtility.getAppDetails(this.endpoint, this.appName); 76 | this._resourceGroupName = appDetails["resourceGroupName"]; 77 | this._kind = appDetails["kind"]; 78 | this._isLinux = this._kind.indexOf('linux') >= 0; 79 | } 80 | 81 | private checkContainerType(){ 82 | let images = this._images.split("\n"); 83 | this._isMultiContainer = false; 84 | if(!!this._multiContainerConfigFile && exist(this._multiContainerConfigFile)){ 85 | let stats: fs.Stats = fs.statSync(this._multiContainerConfigFile); 86 | if(!stats.isFile()) { 87 | throw new Error("Docker-compose file path is incorrect."); 88 | } 89 | else { 90 | this._isMultiContainer = true; 91 | core.debug("Is multi-container app"); 92 | } 93 | 94 | if(!!this._images){ 95 | console.log("Multi-container deployment with the transformation of Docker-Compose file."); 96 | } 97 | else { 98 | console.log("Multi-container deployment without transformation of Docker-Compose file."); 99 | } 100 | } 101 | else if(images.length > 1) { 102 | throw new Error("Multiple images indicate multi-container deployment type, but Docker-compose file is absent.") 103 | } 104 | } 105 | } 106 | 107 | export function exist(path) { 108 | var exist = false; 109 | try { 110 | exist = path && fs.statSync(path) != null; 111 | } 112 | catch (err) { 113 | if (err && err.code === 'ENOENT') { 114 | exist = false; 115 | } 116 | else { 117 | throw err; 118 | } 119 | } 120 | return exist; 121 | } -------------------------------------------------------------------------------- /lib/taskparameters.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importStar = (this && this.__importStar) || function (mod) { 12 | if (mod && mod.__esModule) return mod; 13 | var result = {}; 14 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 15 | result["default"] = mod; 16 | return result; 17 | }; 18 | Object.defineProperty(exports, "__esModule", { value: true }); 19 | const core = __importStar(require("@actions/core")); 20 | const AzureResourceFilterUtility_1 = require("azure-actions-appservice-rest/Utilities/AzureResourceFilterUtility"); 21 | const fs = require("fs"); 22 | class TaskParameters { 23 | constructor(endpoint) { 24 | this._appName = core.getInput('app-name', { required: true }); 25 | this._slotName = core.getInput('slot-name'); 26 | this._images = core.getInput('images'); 27 | this._multiContainerConfigFile = core.getInput('configuration-file'); 28 | this._containerCommand = core.getInput('container-command'); 29 | this._endpoint = endpoint; 30 | this.checkContainerType(); 31 | } 32 | static getTaskParams(endpoint) { 33 | if (!this.taskparams) { 34 | this.taskparams = new TaskParameters(endpoint); 35 | } 36 | return this.taskparams; 37 | } 38 | get appName() { 39 | return this._appName; 40 | } 41 | get slotName() { 42 | return this._slotName; 43 | } 44 | get images() { 45 | return this._images; 46 | } 47 | get resourceGroupName() { 48 | return this._resourceGroupName; 49 | } 50 | get endpoint() { 51 | return this._endpoint; 52 | } 53 | get isLinux() { 54 | return this._isLinux; 55 | } 56 | get isMultiContainer() { 57 | return this._isMultiContainer; 58 | } 59 | get containerCommand() { 60 | return this._containerCommand; 61 | } 62 | get multiContainerConfigFile() { 63 | return this._multiContainerConfigFile; 64 | } 65 | getResourceDetails() { 66 | return __awaiter(this, void 0, void 0, function* () { 67 | let appDetails = yield AzureResourceFilterUtility_1.AzureResourceFilterUtility.getAppDetails(this.endpoint, this.appName); 68 | this._resourceGroupName = appDetails["resourceGroupName"]; 69 | this._kind = appDetails["kind"]; 70 | this._isLinux = this._kind.indexOf('linux') >= 0; 71 | }); 72 | } 73 | checkContainerType() { 74 | let images = this._images.split("\n"); 75 | this._isMultiContainer = false; 76 | if (!!this._multiContainerConfigFile && exist(this._multiContainerConfigFile)) { 77 | let stats = fs.statSync(this._multiContainerConfigFile); 78 | if (!stats.isFile()) { 79 | throw new Error("Docker-compose file path is incorrect."); 80 | } 81 | else { 82 | this._isMultiContainer = true; 83 | core.debug("Is multi-container app"); 84 | } 85 | if (!!this._images) { 86 | console.log("Multi-container deployment with the transformation of Docker-Compose file."); 87 | } 88 | else { 89 | console.log("Multi-container deployment without transformation of Docker-Compose file."); 90 | } 91 | } 92 | else if (images.length > 1) { 93 | throw new Error("Multiple images indicate multi-container deployment type, but Docker-compose file is absent."); 94 | } 95 | } 96 | } 97 | exports.TaskParameters = TaskParameters; 98 | function exist(path) { 99 | var exist = false; 100 | try { 101 | exist = path && fs.statSync(path) != null; 102 | } 103 | catch (err) { 104 | if (err && err.code === 'ENOENT') { 105 | exist = false; 106 | } 107 | else { 108 | throw err; 109 | } 110 | } 111 | return exist; 112 | } 113 | exports.exist = exist; 114 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importStar = (this && this.__importStar) || function (mod) { 12 | if (mod && mod.__esModule) return mod; 13 | var result = {}; 14 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 15 | result["default"] = mod; 16 | return result; 17 | }; 18 | Object.defineProperty(exports, "__esModule", { value: true }); 19 | const core = __importStar(require("@actions/core")); 20 | const crypto = __importStar(require("crypto")); 21 | const AuthorizerFactory_1 = require("azure-actions-webclient/AuthorizerFactory"); 22 | const azure_app_service_1 = require("azure-actions-appservice-rest/Arm/azure-app-service"); 23 | const AzureAppServiceUtility_1 = require("azure-actions-appservice-rest/Utilities/AzureAppServiceUtility"); 24 | const ContainerDeploymentUtility_1 = require("azure-actions-appservice-rest/Utilities/ContainerDeploymentUtility"); 25 | const KuduServiceUtility_1 = require("azure-actions-appservice-rest/Utilities/KuduServiceUtility"); 26 | const taskparameters_1 = require("./taskparameters"); 27 | const AnnotationUtility_1 = require("azure-actions-appservice-rest/Utilities/AnnotationUtility"); 28 | var prefix = !!process.env.AZURE_HTTP_USER_AGENT ? `${process.env.AZURE_HTTP_USER_AGENT}` : ""; 29 | function main() { 30 | return __awaiter(this, void 0, void 0, function* () { 31 | let isDeploymentSuccess = true; 32 | try { 33 | // Set user agent varable 34 | let usrAgentRepo = crypto.createHash('sha256').update(`${process.env.GITHUB_REPOSITORY}`).digest('hex'); 35 | let actionName = 'DeployWebAppToAzure'; 36 | let userAgentString = (!!prefix ? `${prefix}+` : '') + `GITHUBACTIONS_${actionName}_${usrAgentRepo}`; 37 | core.exportVariable('AZURE_HTTP_USER_AGENT', userAgentString); 38 | let endpoint = yield AuthorizerFactory_1.AuthorizerFactory.getAuthorizer(); 39 | var taskParams = taskparameters_1.TaskParameters.getTaskParams(endpoint); 40 | yield taskParams.getResourceDetails(); 41 | core.debug("Predeployment Step Started"); 42 | var appService = new azure_app_service_1.AzureAppService(taskParams.endpoint, taskParams.resourceGroupName, taskParams.appName, taskParams.slotName); 43 | var appServiceUtility = new AzureAppServiceUtility_1.AzureAppServiceUtility(appService); 44 | var kuduService = yield appServiceUtility.getKuduService(); 45 | var kuduServiceUtility = new KuduServiceUtility_1.KuduServiceUtility(kuduService); 46 | core.debug("Deployment Step Started"); 47 | core.debug("Performing container based deployment."); 48 | let containerDeploymentUtility = new ContainerDeploymentUtility_1.ContainerDeploymentUtility(appService); 49 | yield containerDeploymentUtility.deployWebAppImage(taskParams.images, taskParams.multiContainerConfigFile, taskParams.isLinux, taskParams.isMultiContainer, taskParams.containerCommand); 50 | } 51 | catch (error) { 52 | core.debug("Deployment Failed with Error: " + error); 53 | isDeploymentSuccess = false; 54 | core.setFailed(error); 55 | } 56 | finally { 57 | if (!!kuduServiceUtility) { 58 | yield AnnotationUtility_1.addAnnotation(taskParams.endpoint, appService, isDeploymentSuccess); 59 | let activeDeploymentID = yield kuduServiceUtility.updateDeploymentStatus(isDeploymentSuccess, null, { 'type': 'Deployment', slotName: appService.getSlot() }); 60 | core.debug('Active DeploymentId :' + activeDeploymentID); 61 | } 62 | let appServiceApplicationUrl = yield appServiceUtility.getApplicationURL(); 63 | console.log('App Service Application URL: ' + appServiceApplicationUrl); 64 | core.setOutput('webapp-url', appServiceApplicationUrl); 65 | // Reset AZURE_HTTP_USER_AGENT 66 | core.exportVariable('AZURE_HTTP_USER_AGENT', prefix); 67 | core.debug(isDeploymentSuccess ? "Deployment Succeded" : "Deployment failed"); 68 | } 69 | }); 70 | } 71 | main(); 72 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./lib", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": false, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 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 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | }, 60 | "exclude": [ 61 | "./src/Tests" 62 | ] 63 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ***IMPORTANT NOTICE:*** 2 | 3 | ***This action is ARCHIVED and will not receive any updates. Please update your existing workflows to use the action azure/webapps-deploy@v2 which can deploy to both Webapps and Webapp for containers. Refer to https://github.com/Azure/webapps-deploy for more details about the action.*** 4 | ***For example, the action `azure/webapps-container-deploy@v1` should be replaced with `azure/webapps-deploy@v2` in your workflows.*** 5 | 6 | 7 | # GitHub Action for deploying to Azure Web App for Containers 8 | 9 | [GitHub Actions](https://help.github.com/en/articles/about-github-actions) gives you the flexibility to build an automated software development lifecycle workflow. 10 | 11 | You can automate your workflows to deploy [Azure Web Apps for Containers](https://azure.microsoft.com/en-us/services/app-service/containers/) using GitHub Actions. 12 | 13 | Get started today with a [free Azure account](https://azure.com/free/open-source)! 14 | 15 | If you are looking for a Github Action to deploy to an Azure WebApp (Windows or Linux) or Azure WebApp for Containers (single container image or multiple containers), consider using [Azure WebApp](https://github.com/Azure/webapps-deploy/blob/master/action.yml) action. 16 | 17 | For deploying container images to Kubernetes, consider using [Kubernetes deploy](https://github.com/Azure/k8s-deploy) action. This action requires that the cluster context be set earlier in the workflow by using either the [Azure/aks-set-context](https://github.com/Azure/aks-set-context/tree/releases/v1) action or the [Azure/k8s-set-context](https://github.com/Azure/k8s-set-context/tree/releases/v1) action. 18 | 19 | 20 | # End-to-End Sample Workflows 21 | 22 | ## Dependencies on other Github Actions 23 | * [Checkout](https://github.com/actions/checkout) Checkout your Git repository content into Github Actions agent. 24 | * [Azure Login](https://github.com/Azure/login) Login with your Azure credentials for Web app deployment authentication. Once login is done, the next set of Azure actions in the workflow can re-use the same session within the job. 25 | * [docker-login](https://github.com/Azure/docker-login) : Actions to [log in to a private container registry](https://docs.docker.com/engine/reference/commandline/login/) such as [Azure Container registry](https://azure.microsoft.com/en-us/services/container-registry/). Once login is done, the next set of Actions in the workflow can perform tasks such as building, tagging and pushing containers. 26 | 27 | ## Azure Service Principle for RBAC 28 | For using any credentials like Azure Service Principal in your workflow, add them as [secrets](https://help.github.com/en/articles/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables) in the GitHub repository and then refer them in the workflow. 29 | 1. Download Azure CLI from [here](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest), run `az login` to login with your Azure credentials. 30 | 2. Run Azure CLI command to create an [Azure Service Principal for RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview): 31 | ```bash 32 | 33 | az ad sp create-for-rbac --name "myApp" --role contributor \ 34 | --scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} \ 35 | --sdk-auth 36 | 37 | # Replace {subscription-id}, {resource-group} with the subscription, resource group details of the WebApp 38 | # The command should output a JSON object similar to this: 39 | 40 | { 41 | "clientId": "", 42 | "clientSecret": "", 43 | "subscriptionId": "", 44 | "tenantId": "", 45 | (...) 46 | } 47 | ``` 48 | * You can further scope down the Azure Credentials to the Web App using scope attribute. For example, 49 | ``` 50 | az ad sp create-for-rbac --name "myApp" --role contributor \ 51 | --scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Web/sites/{app-name} \ 52 | --sdk-auth 53 | 54 | # Replace {subscription-id}, {resource-group}, and {app-name} with the names of your subscription, resource group, and Azure Web App. 55 | ``` 56 | 3. Paste the json response from above Azure CLI to your Github Repository > Settings > Secrets > Add a new secret > **AZURE_CREDENTIALS** 57 | 4. Now in the workflow file in your branch: `.github/workflows/workflow.yml` replace the secret in Azure login action with your secret (Refer to the example below) 58 | 59 | ## Build and deploy a Node.js app to Azure WebApp Container 60 | 61 | ```yaml 62 | 63 | on: [push] 64 | 65 | name: Linux_Container_Node_Workflow 66 | 67 | jobs: 68 | build-and-deploy: 69 | runs-on: ubuntu-latest 70 | steps: 71 | # checkout the repo 72 | - name: 'Checkout Github Action' 73 | uses: actions/checkout@master 74 | 75 | - name: 'Login via Azure CLI' 76 | uses: azure/login@v1 77 | with: 78 | creds: ${{ secrets.AZURE_CREDENTIALS }} 79 | 80 | - uses: azure/docker-login@v1 81 | with: 82 | login-server: contoso.azurecr.io 83 | username: ${{ secrets.REGISTRY_USERNAME }} 84 | password: ${{ secrets.REGISTRY_PASSWORD }} 85 | 86 | - run: | 87 | docker build . -t contoso.azurecr.io/nodejssampleapp:${{ github.sha }} 88 | docker push contoso.azurecr.io/nodejssampleapp:${{ github.sha }} 89 | 90 | - uses: azure/webapps-deploy@v2 91 | with: 92 | app-name: 'node-rnc' 93 | images: 'contoso.azurecr.io/nodejssampleapp:${{ github.sha }}' 94 | 95 | - name: Azure logout 96 | run: | 97 | az logout 98 | ``` 99 | 100 | # Contributing 101 | 102 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 103 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 104 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 105 | 106 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 107 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 108 | provided by the bot. You will only need to do this once across all repos using our CLA. 109 | 110 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 111 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 112 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # nyc coverage file 140 | .nyc_output/* 141 | coverage/* 142 | 143 | # NCrunch 144 | _NCrunch_* 145 | .*crunch*.local.xml 146 | nCrunchTemp_* 147 | 148 | # MightyMoose 149 | *.mm.* 150 | AutoTest.Net/ 151 | 152 | # Web workbench (sass) 153 | .sass-cache/ 154 | 155 | # Installshield output folder 156 | [Ee]xpress/ 157 | 158 | # DocProject is a documentation generator add-in 159 | DocProject/buildhelp/ 160 | DocProject/Help/*.HxT 161 | DocProject/Help/*.HxC 162 | DocProject/Help/*.hhc 163 | DocProject/Help/*.hhk 164 | DocProject/Help/*.hhp 165 | DocProject/Help/Html2 166 | DocProject/Help/html 167 | 168 | # Click-Once directory 169 | publish/ 170 | 171 | # Publish Web Output 172 | *.[Pp]ublish.xml 173 | *.azurePubxml 174 | # Note: Comment the next line if you want to checkin your web deploy settings, 175 | # but database connection strings (with potential passwords) will be unencrypted 176 | *.pubxml 177 | *.publishproj 178 | 179 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 180 | # checkin your Azure Web App publish settings, but sensitive information contained 181 | # in these scripts will be unencrypted 182 | PublishScripts/ 183 | 184 | # NuGet Packages 185 | *.nupkg 186 | # The packages folder can be ignored because of Package Restore 187 | **/[Pp]ackages/* 188 | # except build/, which is used as an MSBuild target. 189 | !**/[Pp]ackages/build/ 190 | # Uncomment if necessary however generally it will be regenerated when needed 191 | #!**/[Pp]ackages/repositories.config 192 | # NuGet v3's project.json files produces more ignorable files 193 | *.nuget.props 194 | *.nuget.targets 195 | 196 | # Microsoft Azure Build Output 197 | csx/ 198 | *.build.csdef 199 | 200 | # Microsoft Azure Emulator 201 | ecf/ 202 | rcf/ 203 | 204 | # Windows Store app package directories and files 205 | AppPackages/ 206 | BundleArtifacts/ 207 | Package.StoreAssociation.xml 208 | _pkginfo.txt 209 | *.appx 210 | 211 | # Visual Studio cache files 212 | # files ending in .cache can be ignored 213 | *.[Cc]ache 214 | # but keep track of directories ending in .cache 215 | !*.[Cc]ache/ 216 | 217 | # Others 218 | ClientBin/ 219 | ~$* 220 | *~ 221 | *.dbmdl 222 | *.dbproj.schemaview 223 | *.jfm 224 | *.pfx 225 | *.publishsettings 226 | orleans.codegen.cs 227 | 228 | # Including strong name files can present a security risk 229 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 230 | #*.snk 231 | 232 | # Since there are multiple workflows, uncomment next line to ignore bower_components 233 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 234 | #bower_components/ 235 | 236 | # RIA/Silverlight projects 237 | Generated_Code/ 238 | 239 | # Backup & report files from converting an old project file 240 | # to a newer Visual Studio version. Backup files are not needed, 241 | # because we have git ;-) 242 | _UpgradeReport_Files/ 243 | Backup*/ 244 | UpgradeLog*.XML 245 | UpgradeLog*.htm 246 | ServiceFabricBackup/ 247 | *.rptproj.bak 248 | 249 | # SQL Server files 250 | *.mdf 251 | *.ldf 252 | *.ndf 253 | 254 | # Business Intelligence projects 255 | *.rdl.data 256 | *.bim.layout 257 | *.bim_*.settings 258 | *.rptproj.rsuser 259 | 260 | # Microsoft Fakes 261 | FakesAssemblies/ 262 | 263 | # GhostDoc plugin setting file 264 | *.GhostDoc.xml 265 | 266 | # Node.js Tools for Visual Studio 267 | .ntvs_analysis.dat 268 | node_modules/ 269 | 270 | # Visual Studio 6 build log 271 | *.plg 272 | 273 | # Visual Studio 6 workspace options file 274 | *.opt 275 | 276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 277 | *.vbw 278 | 279 | # Visual Studio LightSwitch build output 280 | **/*.HTMLClient/GeneratedArtifacts 281 | **/*.DesktopClient/GeneratedArtifacts 282 | **/*.DesktopClient/ModelManifest.xml 283 | **/*.Server/GeneratedArtifacts 284 | **/*.Server/ModelManifest.xml 285 | _Pvt_Extensions 286 | 287 | # Paket dependency manager 288 | .paket/paket.exe 289 | paket-files/ 290 | 291 | # FAKE - F# Make 292 | .fake/ 293 | 294 | # JetBrains Rider 295 | .idea/ 296 | *.sln.iml 297 | 298 | # CodeRush 299 | .cr/ 300 | 301 | # Python Tools for Visual Studio (PTVS) 302 | __pycache__/ 303 | *.pyc 304 | 305 | # Cake - Uncomment if you are using it 306 | # tools/** 307 | # !tools/packages.config 308 | 309 | # Tabs Studio 310 | *.tss 311 | 312 | # Telerik's JustMock configuration file 313 | *.jmconfig 314 | 315 | # BizTalk build output 316 | *.btp.cs 317 | *.btm.cs 318 | *.odx.cs 319 | *.xsd.cs 320 | 321 | # OpenCover UI analysis results 322 | OpenCover/ 323 | 324 | # Azure Stream Analytics local run output 325 | ASALocalRun/ 326 | 327 | # MSBuild Binary and Structured Log 328 | *.binlog 329 | 330 | # NVidia Nsight GPU debugger configuration file 331 | *.nvuser 332 | 333 | # MFractors (Xamarin productivity tool) working folder 334 | .mfractor/ 335 | --------------------------------------------------------------------------------