{"id":59916,"date":"2020-10-15T10:00:46","date_gmt":"2020-10-15T18:00:46","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/devops\/?p=59916"},"modified":"2020-10-15T09:22:12","modified_gmt":"2020-10-15T17:22:12","slug":"static-web-app-pr-workflow-for-azure-app-service-using-azure-devops","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/devops\/static-web-app-pr-workflow-for-azure-app-service-using-azure-devops\/","title":{"rendered":"Static Web App PR Workflow for Azure App Service Using Azure DevOps"},"content":{"rendered":"<h2>Static Web App PR Workflow for Azure App Service using Azure DevOps<\/h2>\n<p>Microsoft recently announced a new Azure service called <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web Apps<\/a>. <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web Apps<\/a> are tailored for apps with their code in GitHub with static content (html\/javascript\/css) and backend api&#8217;s.<\/p>\n<p>This blog post is <strong>not<\/strong> about <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web Apps<\/a>. It&#8217;s about the amazing pull request workflow that you get right out of the box with <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web Apps<\/a>.<\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web Apps<\/a> use <a href=\"https:\/\/docs.microsoft.com\/azure\/developer\/github\/github-actions?WT.mc_id=devops-9093-abewan\">GitHub Actions<\/a> for CI\/CD and when a user issues a pull request the following happens:<\/p>\n<ul>\n<li>The app is built\/packaged<\/li>\n<li>A staging environment is dynamically provisioned<\/li>\n<li>The app with the PR code is deployed into the newly provisioned staging environment<\/li>\n<li>A comment is added to the pull request with the URL to the newly provisioned staging environment<\/li>\n<\/ul>\n<p>When the pull request is closed<\/p>\n<ul>\n<li>The provisioned staging environment for this pull request is deleted<\/li>\n<\/ul>\n<p>Ok, HOW COOL IS THAT!!!!<\/p>\n<p>That&#8217;s great for <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web Apps<\/a>, but I have a bunch of apps in <a href=\"https:\/\/docs.microsoft.com\/azure\/app-service\/?WT.mc_id=devops-9093-abewan\">Azure App Service<\/a>. The code is in <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/repos\/?WT.mc_id=devops-9093-abewan\">Azure Repos<\/a> and the CI\/CD pipeline uses <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/pipelines\/get-started\/?view=azure-devops&amp;WT.mc_id=devops-9093-abewan\">Azure Pipelines<\/a>. I want that Static Web App pull request workflow for my apps in <a href=\"https:\/\/docs.microsoft.com\/azure\/app-service\/?WT.mc_id=devops-9093-abewan\">Azure App Service<\/a>.<\/p>\n<p>Luckily it wasn&#8217;t hard to do. In this blog post, I&#8217;ll walk you through how I did it. First I&#8217;ll show everything using the classic editor as that looks better than a wall of yml. <strong>And at the end, I&#8217;ll share the yml for my pipeline<\/strong>.<\/p>\n<h2>Making this PR workflow for Azure App Service, Azure Repos and Azure Pipelines<\/h2>\n<p>For this post, I&#8217;ll use a repo that holds a <a href=\"https:\/\/docs.microsoft.com\/dotnet\/fundamentals\/?WT.mc_id=devops-9093-abewan\">.NET Core<\/a> web app that is hosted in <a href=\"https:\/\/docs.microsoft.com\/azure\/app-service\/?WT.mc_id=devops-9093-abewan\">Azure App Service<\/a>. Currently my CI build looks like this:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/bb923c182472586ca0fd64da82f6e6da7a0d99510039b9f658736c1168968ed6-1.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/bb923c182472586ca0fd64da82f6e6da7a0d99510039b9f658736c1168968ed6-1.png\" alt=\"Initial CI Pipeline\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59941\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/bb923c182472586ca0fd64da82f6e6da7a0d99510039b9f658736c1168968ed6-1.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/bb923c182472586ca0fd64da82f6e6da7a0d99510039b9f658736c1168968ed6-1-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/bb923c182472586ca0fd64da82f6e6da7a0d99510039b9f658736c1168968ed6-1-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/bb923c182472586ca0fd64da82f6e6da7a0d99510039b9f658736c1168968ed6-1-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/bb923c182472586ca0fd64da82f6e6da7a0d99510039b9f658736c1168968ed6-1-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<p>Nothing magic about it. This is just your standard <a href=\"https:\/\/docs.microsoft.com\/dotnet\/fundamentals\/?WT.mc_id=devops-9093-abewan\">.NET Core<\/a> build. Any time code is checked into the main branch, this build kicks off.<\/p>\n<h2>Enabling Build For Every Pull Request<\/h2>\n<p>I need this build to kick off any time someone issues a pull request. This can be set in the <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/repos\/git\/branch-policies?view=azure-devops&amp;WT.mc_id=devops-9093-abewan\">branch policies<\/a> for this repository. This <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/repos\/git\/branch-policies?view=azure-devops&amp;WT.mc_id=devops-9093-abewan\">doc<\/a> will walk you through the steps to do this.<\/p>\n<p>After following the above steps, every time a pull request is issued, my CI build is triggered.<\/p>\n<h2>Adding Steps for a Pull Request<\/h2>\n<p>If all the build tasks are successful and if the build trigger is because of a pull request, I want the following to happen:<\/p>\n<ul>\n<li>Provision a staging environment<\/li>\n<li>Deploy my app to the staging environment<\/li>\n<li>Write a comment to the pull request with the url of the newly provisioned slot.<\/li>\n<\/ul>\n<p>Because I&#8217;m using <a href=\"https:\/\/docs.microsoft.com\/azure\/app-service\/?WT.mc_id=devops-9093-abewan\">Azure App Service<\/a>, I can totally use <a href=\"https:\/\/docs.microsoft.com\/azure\/app-service\/deploy-staging-slots?WT.mc_id=devops-9093-abewan\">deployment slots<\/a> for the staging environments. Since there are no tasks that let me dynamically create slots, I&#8217;ll just use the <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/webapp\/deployment\/slot?view=azure-cli-latest&amp;WT.mc_id=devops-9093-abewan#az_webapp_deployment_slot_create\">Azure CLI to dynamically create my slot<\/a>. Once it is created, I can use a normal App Service deploy task to deploy my app to the slot. And to write a comment to the pull request, I&#8217;ll use the <a href=\"https:\/\/docs.microsoft.com\/rest\/api\/azure\/devops\/?view=azure-devops-rest-6.1&amp;WT.mc_id=devops-9093-abewan\">Azure DevOps REST API<\/a>.<\/p>\n<h3>Creating a Task to Provision the Staging Slot<\/h3>\n<p>To provision a new staging slot, I&#8217;ll use the Bash Script task and from there, use the <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/?WT.mc_id=devops-9093-abewan\">Azure CLI<\/a> to provision my slot.<\/p>\n<p>I also want this task to run only if all the previous tasks were successful and the reason for this build is a pull request. To do this, I drag a Bash Script Task to the end and I set the Control Option > Run this task to<\/p>\n<pre><code>Custom conditions\n<\/code><\/pre>\n<p>and I set Control Option > Custom condition to<\/p>\n<pre><code>and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))\n<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/cce9f2d2fda9bd1df06573ff9979f3238ec5e7816f15269d29214e5643fe2be2.png\" alt=\"Control Options Settings\" width=\"1920\" height=\"977\" class=\"size-full wp-image-59943\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/cce9f2d2fda9bd1df06573ff9979f3238ec5e7816f15269d29214e5643fe2be2.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/cce9f2d2fda9bd1df06573ff9979f3238ec5e7816f15269d29214e5643fe2be2-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/cce9f2d2fda9bd1df06573ff9979f3238ec5e7816f15269d29214e5643fe2be2-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/cce9f2d2fda9bd1df06573ff9979f3238ec5e7816f15269d29214e5643fe2be2-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/cce9f2d2fda9bd1df06573ff9979f3238ec5e7816f15269d29214e5643fe2be2-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/p>\n<p>Next, let&#8217;s give this task a better name<\/p>\n<pre><code>Create CI Staging Slot For PR\n<\/code><\/pre>\n<p>and make sure Inline is selected<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0cd7501b662408a549d17c87f76f9264b3858a68365ea04db5966a69e4a24303.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0cd7501b662408a549d17c87f76f9264b3858a68365ea04db5966a69e4a24303.png\" alt=\"Inline Bash Task\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59951\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0cd7501b662408a549d17c87f76f9264b3858a68365ea04db5966a69e4a24303.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0cd7501b662408a549d17c87f76f9264b3858a68365ea04db5966a69e4a24303-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0cd7501b662408a549d17c87f76f9264b3858a68365ea04db5966a69e4a24303-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0cd7501b662408a549d17c87f76f9264b3858a68365ea04db5966a69e4a24303-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0cd7501b662408a549d17c87f76f9264b3858a68365ea04db5966a69e4a24303-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<p>In the Script text box, that&#8217;s where I will drop in my bash script which calls the <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/?WT.mc_id=devops-9093-abewan\">Azure CLI<\/a> to provision the new slot.<\/p>\n<p>To do that, I will need some pipeline variables. First, to use the <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/?WT.mc_id=devops-9093-abewan\">Azure CLI<\/a>, I will need to login using an <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/create-an-azure-service-principal-azure-cli?WT.mc_id=devops-9093-abewan\">Azure Service Principal<\/a>. Follow the <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/create-an-azure-service-principal-azure-cli?WT.mc_id=devops-9093-abewan\">link<\/a> to create a service principal. Next, to create a slot, I will need the app service name as well as the resource group the app service is in.<\/p>\n<p>I add these variables to the variable section of my pipeline. For my service principal password, I encrypt it using the lock function. I usually store all my secrets in key vault, but to simplify this example, I&#8217;ll just dump all my variables in the variable section of the pipeline.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/690d9662f1ea9b88bfe9477d34a6e526f30e49f7e6f32b8136e048c4927d9759.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/690d9662f1ea9b88bfe9477d34a6e526f30e49f7e6f32b8136e048c4927d9759.png\" alt=\"VariablesInPipeline\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59953\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/690d9662f1ea9b88bfe9477d34a6e526f30e49f7e6f32b8136e048c4927d9759.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/690d9662f1ea9b88bfe9477d34a6e526f30e49f7e6f32b8136e048c4927d9759-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/690d9662f1ea9b88bfe9477d34a6e526f30e49f7e6f32b8136e048c4927d9759-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/690d9662f1ea9b88bfe9477d34a6e526f30e49f7e6f32b8136e048c4927d9759-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/690d9662f1ea9b88bfe9477d34a6e526f30e49f7e6f32b8136e048c4927d9759-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<p>Finally, for the script to provisions a new deployment slot, I need to do the following:<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/authenticate-azure-cli?WT.mc_id=devops-9093-abewan\">Login to Azure<\/a> using my <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/create-an-azure-service-principal-azure-cli?WT.mc_id=devops-9093-abewan\">Service Principal<\/a> and the <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/?WT.mc_id=devops-9093-abewan\">Azure CLI<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/webapp\/deployment\/slot?view=azure-cli-latest&amp;WT.mc_id=devops-9093-abewan#az_webapp_deployment_slot_create\">Creating Slots with Azure CLI<\/a> for the pull request<\/li>\n<\/ul>\n<p>Because I can have multiple pull request happening at the same time and each slot must have a unique slot name, I&#8217;ll use the pull request ID as part of my slot name. I&#8217;ll name my slot<\/p>\n<pre><code>staging-pr-$(System.PullRequest.PullRequestId)\n<\/code><\/pre>\n<p><code>System.PullRequest.PullRequestId<\/code> is a <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/pipelines\/build\/variables?view=azure-devops&amp;tabs=classic&amp;WT.mc_id=devops-9093-abewan\">built in pipeline variable<\/a><\/p>\n<p>In the Script text field, I add the following script:<\/p>\n<pre><code>   # Login to Azure using service principal\n   echo 'Logging into Azure with service principal...'\n   az login \\\n     --service-principal \\\n     --username $(servicePrincipal.name) \\\n     --tenant $(servicePrincipal.tenant) \\\n     --password $(servicePrincipal.password)\n   echo 'Logged into Azure'\n   echo ''\n\n   # Create staging slot for PR\n   echo 'Creating staging slot for PR...'\n   az webapp deployment slot create \\\n       --name $(webApp.name) \\\n       --resource-group $(webApp.resourceGroup) \\\n       --slot staging-pr-$(System.PullRequest.PullRequestId)\n   echo \"Created staging slot for PR \" $(System.PullRequest.PullRequestId)\n<\/code><\/pre>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ec7941f7a3b8f4228e452e5531a6bfc810409157183a6baabfc68eba101239e.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ec7941f7a3b8f4228e452e5531a6bfc810409157183a6baabfc68eba101239e.png\" alt=\"Create Staging Slot\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59954\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ec7941f7a3b8f4228e452e5531a6bfc810409157183a6baabfc68eba101239e.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ec7941f7a3b8f4228e452e5531a6bfc810409157183a6baabfc68eba101239e-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ec7941f7a3b8f4228e452e5531a6bfc810409157183a6baabfc68eba101239e-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ec7941f7a3b8f4228e452e5531a6bfc810409157183a6baabfc68eba101239e-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ec7941f7a3b8f4228e452e5531a6bfc810409157183a6baabfc68eba101239e-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<h3>Deploy App To The Newly Provisioned Slot<\/h3>\n<p>To deploy my app to the slot, I just use a standard App Service deploy task. Like the previous task, I only want this task to run if all my previous tasks were successful and this build is triggered by a pull request. So I set Control Option > Run this task to<\/p>\n<pre><code>Custom conditions\n<\/code><\/pre>\n<p>and I set Control Option > Custom condition to<\/p>\n<pre><code>and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))\n<\/code><\/pre>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f31326505826ff8555eaba9c96d283fbdd82f400667c6f6b69490e7cb9d83554.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f31326505826ff8555eaba9c96d283fbdd82f400667c6f6b69490e7cb9d83554.png\" alt=\"Control Options For Deploy Task\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59955\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f31326505826ff8555eaba9c96d283fbdd82f400667c6f6b69490e7cb9d83554.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f31326505826ff8555eaba9c96d283fbdd82f400667c6f6b69490e7cb9d83554-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f31326505826ff8555eaba9c96d283fbdd82f400667c6f6b69490e7cb9d83554-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f31326505826ff8555eaba9c96d283fbdd82f400667c6f6b69490e7cb9d83554-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f31326505826ff8555eaba9c96d283fbdd82f400667c6f6b69490e7cb9d83554-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<p>Next, I give this task a nicer name and set the parameters so I&#8217;m deploying to the slot. Everything here is pretty standard except the slot name is dynamic based off of the pull request ID. So for the Slot text field, I put in<\/p>\n<pre><code>staging-pr-$(System.PullRequest.PullRequestId)\n<\/code><\/pre>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f2ed80cc652c6b58518de3873820a7376722641906d3544c160d95209d5dde7b.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f2ed80cc652c6b58518de3873820a7376722641906d3544c160d95209d5dde7b.png\" alt=\"Slot Name In Deploy Task\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59956\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f2ed80cc652c6b58518de3873820a7376722641906d3544c160d95209d5dde7b.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f2ed80cc652c6b58518de3873820a7376722641906d3544c160d95209d5dde7b-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f2ed80cc652c6b58518de3873820a7376722641906d3544c160d95209d5dde7b-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f2ed80cc652c6b58518de3873820a7376722641906d3544c160d95209d5dde7b-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/f2ed80cc652c6b58518de3873820a7376722641906d3544c160d95209d5dde7b-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<h3>Write the URL of the Staging Slot as a PR Comment<\/h3>\n<p>To write the URL of the staging slot as a pull request comment, I&#8217;ll again use a bash script task to call the <a href=\"https:\/\/docs.microsoft.com\/rest\/api\/azure\/devops\/?view=azure-devops-rest-6.1&amp;WT.mc_id=devops-9093-abewan\">Azure DevOps REST API<\/a>. This means I&#8217;ll need to use an <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/organizations\/accounts\/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;tabs=preview-page&amp;WT.mc_id=devops-9093-abewan\">Azure DevOps PAT<\/a>. I create a PAT using <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/organizations\/accounts\/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;tabs=preview-page&amp;WT.mc_id=devops-9093-abewan\">this<\/a>, and then I stick it into the variables section of the pipeline and encrypt it.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ece48164cab18b898dfa16e474e2f215a0d0b64d8238e7a822302ed4530a9cd.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ece48164cab18b898dfa16e474e2f215a0d0b64d8238e7a822302ed4530a9cd.png\" alt=\"Pipeline Variable Section For Bash Task To Add Message to AzD\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59957\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ece48164cab18b898dfa16e474e2f215a0d0b64d8238e7a822302ed4530a9cd.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ece48164cab18b898dfa16e474e2f215a0d0b64d8238e7a822302ed4530a9cd-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ece48164cab18b898dfa16e474e2f215a0d0b64d8238e7a822302ed4530a9cd-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ece48164cab18b898dfa16e474e2f215a0d0b64d8238e7a822302ed4530a9cd-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/2ece48164cab18b898dfa16e474e2f215a0d0b64d8238e7a822302ed4530a9cd-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<p>Next, I drag a bash task to the end and again, this task should only run if all the previous tasks were successful and the build was triggered by a pull request. So I set Control Option > Run this task to<\/p>\n<pre><code>Custom conditions\n<\/code><\/pre>\n<p>and I set Control Option > Custom condition to<\/p>\n<pre><code>and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))\n<\/code><\/pre>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/7d3b6c8ad30759426b3180dfc11ea63d76071237cb81a94510e721b46c301ef1.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/7d3b6c8ad30759426b3180dfc11ea63d76071237cb81a94510e721b46c301ef1.png\" alt=\"Control Option For Messaging Task\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59958\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/7d3b6c8ad30759426b3180dfc11ea63d76071237cb81a94510e721b46c301ef1.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/7d3b6c8ad30759426b3180dfc11ea63d76071237cb81a94510e721b46c301ef1-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/7d3b6c8ad30759426b3180dfc11ea63d76071237cb81a94510e721b46c301ef1-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/7d3b6c8ad30759426b3180dfc11ea63d76071237cb81a94510e721b46c301ef1-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/7d3b6c8ad30759426b3180dfc11ea63d76071237cb81a94510e721b46c301ef1-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<p>Next, I give this task a better name<\/p>\n<pre><code>Add staging URL to PR Comment\n<\/code><\/pre>\n<p>select<\/p>\n<pre><code>Inline\n<\/code><\/pre>\n<p>and in the Script textbox, I add the bash script which uses the Azure DevOps Rest API to add the comment to the pull request.<\/p>\n<pre><code># Add PR comment with link to staging\necho \"Adding PR comment with link to staging environment...\"\nmessage=\"Created staging environment for PR - https:\/\/$(webApp.name)-staging-pr-\"$(System.PullRequest.PullRequestId)\".azurewebsites.net\/\"\necho \"message: $message\"\n\n# post message to PR using azure devops rest api\ncurl -i --user :$(azureDevOpsPAT) -H \"Content-Type: application\/json\" -H \"Accept: aon\" -X POST -d '{\"comments\": [{ \"parentCommentId\":0, \"content\": \"'\"$message\"'\", \"commentType\": 1}], \"status\": 1}' $(System.TeamFoundationCollectionUri)\/$(System.TeamProject)\/_apis\/git\/repositories\/$(Build.Repository.ID)\/pullRequests\/$(System.PullRequest.PullRequestId)\/threads?api-version=6.1-preview.1\n\necho \"Done adding PR comment with link to staging environment\"\n<\/code><\/pre>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/179f146782b38ace5612a55adebfb4566db5ce0fb4e9c51ca55483f10f9eea68.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/179f146782b38ace5612a55adebfb4566db5ce0fb4e9c51ca55483f10f9eea68.png\" alt=\"Inline Bash Task For Messaging\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59959\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/179f146782b38ace5612a55adebfb4566db5ce0fb4e9c51ca55483f10f9eea68.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/179f146782b38ace5612a55adebfb4566db5ce0fb4e9c51ca55483f10f9eea68-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/179f146782b38ace5612a55adebfb4566db5ce0fb4e9c51ca55483f10f9eea68-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/179f146782b38ace5612a55adebfb4566db5ce0fb4e9c51ca55483f10f9eea68-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/179f146782b38ace5612a55adebfb4566db5ce0fb4e9c51ca55483f10f9eea68-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<h2>Add Step For When PR Closes<\/h2>\n<p>I need to add one final step. When the pull request is closed and the code is merged to master, I need to clean up my staging environment and <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/webapp?view=azure-cli-latest&amp;WT.mc_id=devops-9093-abewan#az_webapp_delete\">delete the slot<\/a> for the specific pull request.<\/p>\n<p>Azure DevOps doesn&#8217;t have any easy triggers in the pipeline for when you close a pull request. You could do this with <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/service-hooks\/services\/webhooks?view=azure-devops&amp;WT.mc_id=devops-9093-abewan\">Azure DevOps Web Hooks<\/a>. But this would require me to use something like <a href=\"https:\/\/docs.microsoft.com\/azure\/azure-functions\/functions-overview?WT.mc_id=devops-9093-abewan\">Azure Functions<\/a> to do the pull request cleanup\/delete slot logic. I really wanted to keep everything within my pipelines so I&#8217;m just triggering this task based on the version control merge message. Whenever a pull request is merged, the merge message will start with <code>Merged PR xxx<\/code> where xxx is the pull request ID.<\/p>\n<p>I drag a bash task onto the board and I only want this task to run if all the previous tasks were successful and if the commit message starts wtih <code>Merged PR<\/code>. So I set Control Option > Run this task to<\/p>\n<pre><code>Custom conditions\n<\/code><\/pre>\n<p>and I set Control Option > Custom condition to<\/p>\n<pre><code>and(succeeded(), startsWith(variables['Build.SourceVersionMessage'], 'Merged PR '))\n<\/code><\/pre>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6e831e183b98ed36a8b7cd9c1e32103a27e3796a356788d33a3a6f2c04fd37.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6e831e183b98ed36a8b7cd9c1e32103a27e3796a356788d33a3a6f2c04fd37.png\" alt=\"Control Option For Cleanup Task\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59960\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6e831e183b98ed36a8b7cd9c1e32103a27e3796a356788d33a3a6f2c04fd37.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6e831e183b98ed36a8b7cd9c1e32103a27e3796a356788d33a3a6f2c04fd37-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6e831e183b98ed36a8b7cd9c1e32103a27e3796a356788d33a3a6f2c04fd37-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6e831e183b98ed36a8b7cd9c1e32103a27e3796a356788d33a3a6f2c04fd37-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6e831e183b98ed36a8b7cd9c1e32103a27e3796a356788d33a3a6f2c04fd37-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<p>I give this task a better name, make sure inline is selected and in the Script text box, I add the bash script which uses the <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/webapp?view=azure-cli-latest&amp;WT.mc_id=devops-9093-abewan#az_webapp_delete\">Azure CLI to delete the right staging slot<\/a>.<\/p>\n<pre><code># PR Closed, changes merged to main\necho 'PR close, changes merged to main'\n\n# get the source version message\nsourceVersionMessage=\"$(Build.SourceVersionMessage)\"\necho 'build source version message: ' $sourceVersionMessage\n\n# parse the PR number from the source version message\nprNumber=$(echo $sourceVersionMessage| sed -r 's\/^([^.]+).*$\/\\1\/; s\/^[^0-9]*([0-9]+).*$\/\\1\/')\necho 'prNumber: ' $prNumber\n\n# Login to Azure using service principal\necho 'Logging into Azure with service principal...'\naz login \\\n  --service-principal \\\n  --username $(servicePrincipal.name) \\\n  --tenant $(servicePrincipal.tenant) \\\n  --password $SERVICE_PRINCIPAL_PASSWORD\necho 'Logged into Azure'\necho ''\n\n# Delete PR staging slot\necho 'Deleting staging slot for PR ' $prNumber '...'\naz webapp deployment slot delete \\\n  --name $(webApp.name) \\\n  --resource-group $(webApp.resourceGroup) \\\n  --slot staging-pr-$prNumber\necho 'Deleted slot for PR ' $prNumber\n<\/code><\/pre>\n<h2>Complete CI Pipeline<\/h2>\n<p>Now, my CI build does everything the <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web App<\/a> pull request workflow does and it looks like this:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0c58c39bc1c07beef35e65fd0e5deebb0a70f5b969d7730dfcb5e8f7af7267be.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0c58c39bc1c07beef35e65fd0e5deebb0a70f5b969d7730dfcb5e8f7af7267be.png\" alt=\"Complete CI Workflow\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59961\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0c58c39bc1c07beef35e65fd0e5deebb0a70f5b969d7730dfcb5e8f7af7267be.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0c58c39bc1c07beef35e65fd0e5deebb0a70f5b969d7730dfcb5e8f7af7267be-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0c58c39bc1c07beef35e65fd0e5deebb0a70f5b969d7730dfcb5e8f7af7267be-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0c58c39bc1c07beef35e65fd0e5deebb0a70f5b969d7730dfcb5e8f7af7267be-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/0c58c39bc1c07beef35e65fd0e5deebb0a70f5b969d7730dfcb5e8f7af7267be-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<h2>What about YML pipelines?<\/h2>\n<p>Yes, you can do all this using YML pipelines. In fact, that&#8217;s how I would normally do this. However, that classic editor sure looks nicer than a wall of yml so I figured I&#8217;d start with the classic editor. Here is my YML<\/p>\n<pre><code>trigger:\n- master\n\npool:\n  vmImage: 'ubuntu-latest'\n\nvariables:\n  servicePrincipal.name: 'http:\/\/AbelDeployDemoBackupPrincipal'\n  servicePrincipal.tenant: '72f988bf-86f1-41af-91ab-2d7cd011db47'\n  webApp.name: 'PRWorkflowBlogAppService'\n  webApp.resourceGroup: 'PRWorkflowBlog'\n  BuildConfiguration: 'release'\n\nsteps:\n\n# Use .NET core 5 sdk\n- task: UseDotNet@2\n  displayName: 'Use .NET Core 5 SDK'\n  inputs:\n    version: 5.x\n    includePreviewVersions: true\n\n# Build .net core project    \n- task: DotNetCoreCLI@2\n  displayName: Build\n  inputs:\n    projects: '$(Parameters.RestoreBuildProjects)'\n    arguments: '--configuration $(BuildConfiguration)'\n\n# Restore project\n- task: DotNetCoreCLI@2\n  displayName: Build\n  inputs:\n    projects: '$(Parameters.RestoreBuildProjects)'\n    arguments: '--configuration $(BuildConfiguration)'\n\n# Run unit tests\n- task: DotNetCoreCLI@2\n  displayName: Test\n  inputs:\n    command: test\n    projects: '$(Parameters.TestProjects)'\n    arguments: '--configuration $(BuildConfiguration)'\n\n# Publish .net core app to staging directory\n- task: DotNetCoreCLI@2\n  displayName: Publish\n  inputs:\n    command: publish\n    publishWebProjects: True\n    arguments: '-r win-x64 --self-contained true --configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'\n\n# Publish build bits as build artifact\n- task: PublishBuildArtifacts@1\n  displayName: 'Publish Artifact'\n  inputs:\n    PathtoPublish: '$(build.artifactstagingdirectory)'\n  condition: succeededOrFailed()\n\n# Create CI Staging Slot for PR\n- bash: |\n   # Login to Azure using service principal\n   echo 'Logging into Azure with service principal...'\n   az login \\\n     --service-principal \\\n     --username $(servicePrincipal.name) \\\n     --tenant $(servicePrincipal.tenant) \\\n     --password $SERVICE_PRINCIPAL_PASSWORD\n   echo 'Logged into Azure'\n   echo ''\n\n   # Create staging slot for PR\n   echo 'Creating staging slot for PR...'\n   az webapp deployment slot create \\\n       --name $(webApp.name) \\\n       --resource-group $(webApp.resourceGroup) \\\n       --slot staging-pr-$(System.PullRequest.PullRequestId)\n   echo \"Created staging slot for PR \" $(System.PullRequest.PullRequestId)\n\n  displayName: 'Create CI Staging Slot For PR'\n  condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))\n  env:\n    SERVICE_PRINCIPAL_PASSWORD: $(servicePrincipal.password)\n\n# Add staging URL to PR comment\n- bash: |\n   # Add PR comment with link to staging\n   echo \"Adding PR comment with link to staging environment...\"\n   message=\"Created staging environment for PR - https:\/\/$(webApp.name)-staging-pr-\"$(System.PullRequest.PullRequestId)\".azurewebsites.net\/\"\n   echo \"message: $message\"\n\n   # post message to PR using azure devops rest api\n   curl -i --user :$AZURE_DEVOPS_PAT -H \"Content-Type: application\/json\" -H \"Accept: aon\" -X POST -d '{\"comments\": [{ \"parentCommentId\":0, \"content\": \"'\"$message\"'\", \"commentType\": 1}], \"status\": 1}' $(System.TeamFoundationCollectionUri)\/$(System.TeamProject)\/_apis\/git\/repositories\/$(Build.Repository.ID)\/pullRequests\/$(System.PullRequest.PullRequestId)\/threads?api-version=6.1-preview.1\n\n   echo \"Done adding PR comment with link to staging environment\"\n  displayName: 'Add staging URL to PR Comment'\n  condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))\n  env:\n    AZURE_DEVOPS_PAT: $(azureDevOpsPAT)\n\n# Deploy build to staging environment\n- task: AzureRmWebAppDeployment@4\n  displayName: 'Deploy to PR Staging'\n  inputs:\n    ConnectionType: 'AzureRM'\n    azureSubscription: 'AbelDemosSubscription(2f42eaaf-1883-462f-a04f-a1d88fa6fd44)'\n    appType: 'webApp'\n    WebAppName: 'PRWorkflowBlogAppService'\n    deployToSlotOrASE: true\n    ResourceGroupName: 'PRWorkflowBlog'\n    SlotName: 'staging-pr-$(System.PullRequest.PullRequestId)'\n    packageForLinux: '$(System.DefaultWorkingDirectory)\/**\/netlandingpage.zip'\n    enableCustomDeployment: true\n    DeploymentType: 'webDeploy'\n    RemoveAdditionalFilesFlag: true\n    ExcludeFilesFromAppDataFlag: false\n  condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))\n\n\n\n\n# Delete staging slot\n- bash: |\n   # PR Closed, changes merged to main\n   echo 'PR close, changes merged to main'\n\n   # get the source version message\n   sourceVersionMessage=\"$(Build.SourceVersionMessage)\"\n   echo 'build source version message: ' $sourceVersionMessage\n\n   # parse the PR number from the source version message\n   prNumber=$(echo $sourceVersionMessage| sed -r 's\/^([^.]+).*$\/\\1\/; s\/^[^0-9]*([0-9]+).*$\/\\1\/')\n   echo 'prNumber: ' $prNumber\n\n   # Login to Azure using service principal\n   echo 'Logging into Azure with service principal...'\n   az login \\\n     --service-principal \\\n     --username $(servicePrincipal.name) \\\n     --tenant $(servicePrincipal.tenant) \\\n     --password $SERVICE_PRINCIPAL_PASSWORD\n   echo 'Logged into Azure'\n   echo ''\n\n   # Delete PR staging slot\n   echo 'Deleting staging slot for PR ' $prNumber '...'\n   az webapp deployment slot delete \\\n     --name $(webApp.name) \\\n     --resource-group $(webApp.resourceGroup) \\\n     --slot staging-pr-$prNumber\n   echo 'Deleted slot for PR ' $prNumber\n  displayName: 'Delete Staging Slot For PR'\n  condition: and(succeeded(), startsWith(variables['Build.SourceVersionMessage'], 'Merged PR '))\n  env:\n    SERVICE_PRINCIPAL_PASSWORD: $(servicePrincipal.password)\n<\/code><\/pre>\n<p>Secrets should not be stored in the YML so my <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/organizations\/accounts\/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;tabs=preview-page&amp;WT.mc_id=devops-9093-abewan\">Azure DevOps PAT<\/a> and my password for my <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/create-an-azure-service-principal-azure-cli?WT.mc_id=devops-9093-abewan\">Azure Service Principal<\/a> are saved as encrypted variables in the pipeline.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6c5b141aea565a8f9490d2aaf0a9562a0e66ad7404831453718d965199c6ef.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6c5b141aea565a8f9490d2aaf0a9562a0e66ad7404831453718d965199c6ef.png\" alt=\"Secret Variables In YML pipeline\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59963\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6c5b141aea565a8f9490d2aaf0a9562a0e66ad7404831453718d965199c6ef.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6c5b141aea565a8f9490d2aaf0a9562a0e66ad7404831453718d965199c6ef-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6c5b141aea565a8f9490d2aaf0a9562a0e66ad7404831453718d965199c6ef-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6c5b141aea565a8f9490d2aaf0a9562a0e66ad7404831453718d965199c6ef-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/ad6c5b141aea565a8f9490d2aaf0a9562a0e66ad7404831453718d965199c6ef-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<p>Secret variables are not automatically decrypted in YML pipelines and need to be passed to your YML with <code>env:<\/code>. <a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/pipelines\/process\/variables?view=azure-devops&amp;WT.mc_id=devops-9093-abewan&amp;tabs=yaml%2Cbatch\">This<\/a> shows how to do it.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/8ee25357a5b433e26fdf86bed613d06701856c75c2e2e21aaaba26e279c73e4f.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/8ee25357a5b433e26fdf86bed613d06701856c75c2e2e21aaaba26e279c73e4f.png\" alt=\"Using Secrets in YML example\" width=\"1920\" height=\"977\" class=\"alignnone size-full wp-image-59962\" srcset=\"https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/8ee25357a5b433e26fdf86bed613d06701856c75c2e2e21aaaba26e279c73e4f.png 1920w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/8ee25357a5b433e26fdf86bed613d06701856c75c2e2e21aaaba26e279c73e4f-300x153.png 300w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/8ee25357a5b433e26fdf86bed613d06701856c75c2e2e21aaaba26e279c73e4f-1024x521.png 1024w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/8ee25357a5b433e26fdf86bed613d06701856c75c2e2e21aaaba26e279c73e4f-768x391.png 768w, https:\/\/devblogs.microsoft.com\/devops\/wp-content\/uploads\/sites\/6\/2020\/10\/8ee25357a5b433e26fdf86bed613d06701856c75c2e2e21aaaba26e279c73e4f-1536x782.png 1536w\" sizes=\"(max-width: 1920px) 100vw, 1920px\" \/><\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>My CI pipeline now perfectly emulates the <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web Apps<\/a> pull request workflow. When a pull request happens, my CI build will trigger and the following will occur<\/p>\n<ul>\n<li>Use the .net core 5 sdk<\/li>\n<li>Restore<\/li>\n<li>Build app<\/li>\n<li>Run unit tests<\/li>\n<li>Publish to staging directory<\/li>\n<li>Publish staging as a build artifact<\/li>\n<li>Create a CI staging slot for this specific PR using the PR ID as part of the slot name<\/li>\n<li>Deploy the app to this specific staging slot<\/li>\n<li>Write the URL to this staging slot as a pull request comment<\/li>\n<\/ul>\n<p>When this pull request is closed out and the changes merged to master, this build will trigger and the following will occure<\/p>\n<ul>\n<li>Use the .net core 5 sdk<\/li>\n<li>Restore<\/li>\n<li>Build app<\/li>\n<li>Run unit tests<\/li>\n<li>Publish to staging directory<\/li>\n<li>Publish staging as a build artifact<\/li>\n<li>Delete the PR staging slot<\/li>\n<\/ul>\n<p>To figure out how to do this, I used the same strategy I use for any pipeline I craft. I ask myself, &#8220;How would I do this from the command line?&#8221; If there are pre-built tasks that do what I need, I use those. And if there are not, I use a bash or powershell task to execute the command line calls. For this example to implement the <a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web Apps<\/a> pull request workflow from the command line, I needed to use the <a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/?WT.mc_id=devops-9093-abewan\">Azure CLI<\/a> and the <a href=\"https:\/\/docs.microsoft.com\/rest\/api\/azure\/devops\/?view=azure-devops-rest-6.1&amp;WT.mc_id=devops-9093-abewan\">Azure DevOps REST API<\/a>. I then sat down at a command prompt and figured out the exact commands to call, and then copied those into a bash task.<\/p>\n<p>Don&#8217;t forget about the edge cases. What happens when multiple pull requests happen at once? What happens if tasks in the workflow fail? What happens when a pull request merge happens to the main branch and the staging environment doesn&#8217;t exist? Things like that.But with a little care, your pipeline will be able to handle all situations.<\/p>\n<h2>Related Links<\/h2>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/static-web-apps\/?WT.mc_id=devops-9093-abewan\">Static Web Apps<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/developer\/github\/github-actions?WT.mc_id=devops-9093-abewan\">GitHub Actions<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/app-service\/?WT.mc_id=devops-9093-abewan\">Azure App Service<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/repos\/?WT.mc_id=devops-9093-abewan\">Azure Repos<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/pipelines\/get-started\/?view=azure-devops&amp;WT.mc_id=devops-9093-abewan\">Azure Pipelines<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/dotnet\/fundamentals\/?WT.mc_id=devops-9093-abewan\">.NET Core<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/repos\/git\/branch-policies?view=azure-devops&amp;WT.mc_id=devops-9093-abewan\">Branch Policies<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/app-service\/deploy-staging-slots?WT.mc_id=devops-9093-abewan\">Staging with Deployment Slots<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/?WT.mc_id=devops-9093-abewan\">Azure CLI<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/authenticate-azure-cli?WT.mc_id=devops-9093-abewan\">Login to Azure<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/webapp\/deployment\/slot?view=azure-cli-latest&amp;WT.mc_id=devops-9093-abewan#az_webapp_deployment_slot_create\">Creating Slots with Azure CLI<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/create-an-azure-service-principal-azure-cli?WT.mc_id=devops-9093-abewan\">Azure Service Principal<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/pipelines\/build\/variables?view=azure-devops&amp;tabs=classic&amp;WT.mc_id=devops-9093-abewan\">Pipeline Built In Variables<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/rest\/api\/azure\/devops\/?view=azure-devops-rest-6.1&amp;WT.mc_id=devops-9093-abewan\">Azure DevOps REST API<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/rest\/api\/azure\/devops\/git\/pull%20request%20threads\/create?view=azure-devops-rest-6.1&amp;WT.mc_id=devops-9093-abewan\">Write Comment To Pull Request<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/organizations\/accounts\/use-personal-access-tokens-to-authenticate?view=azure-devops&amp;tabs=preview-page&amp;WT.mc_id=devops-9093-abewan\">Azure DevOps PAT<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/service-hooks\/services\/webhooks?view=azure-devops&amp;WT.mc_id=devops-9093-abewan\">Azure DevOps Web Hooks<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/azure-functions\/functions-overview?WT.mc_id=devops-9093-abewan\">Azure Functions<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/cli\/azure\/webapp?view=azure-cli-latest&amp;WT.mc_id=devops-9093-abewan#az_webapp_delete\">Delete Slot with the Azure CLI<\/a><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/azure\/devops\/pipelines\/process\/variables?view=azure-devops&amp;WT.mc_id=devops-9093-abewan&amp;tabs=yaml%2Cbatch\">Using Secret Variables In YML Pipelines<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The pull request workflow in Static Web Apps is super cool, but that only works for Static Web Apps and GitHub Actions.. This blog post walks you through how to implement the same PR workflow for Azure App Service using Azure DevOps<\/p>\n","protected":false},"author":226,"featured_media":45953,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[227,224,226,1,225],"tags":[],"class_list":["post-59916","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-agile","category-azure","category-ci","category-devops","category-git"],"acf":[],"blog_post_summary":"<p>The pull request workflow in Static Web Apps is super cool, but that only works for Static Web Apps and GitHub Actions.. This blog post walks you through how to implement the same PR workflow for Azure App Service using Azure DevOps<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/posts\/59916","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/users\/226"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/comments?post=59916"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/posts\/59916\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/media\/45953"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/media?parent=59916"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/categories?post=59916"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/devops\/wp-json\/wp\/v2\/tags?post=59916"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}