<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Napoleon 'Ike' Jones</title>
    <description>The latest articles on DEV Community by Napoleon 'Ike' Jones (@ikethedev).</description>
    <link>https://dev.to/ikethedev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F223330%2F0db52e14-91ee-4972-a121-470172004507.jpg</url>
      <title>DEV Community: Napoleon 'Ike' Jones</title>
      <link>https://dev.to/ikethedev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ikethedev"/>
    <language>en</language>
    <item>
      <title>Deploying a .NET Core App to AWS Elastic Beanstalk via Azure DevOps </title>
      <dc:creator>Napoleon 'Ike' Jones</dc:creator>
      <pubDate>Sun, 02 Feb 2020 18:52:43 +0000</pubDate>
      <link>https://dev.to/ikethedev/deploying-a-net-core-app-to-aws-elastic-beanstalk-via-azure-devops-5ab8</link>
      <guid>https://dev.to/ikethedev/deploying-a-net-core-app-to-aws-elastic-beanstalk-via-azure-devops-5ab8</guid>
      <description>&lt;p&gt;Deploying a .NET Core App to AWS Elastic Beanstalk via Azure DevOps&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In another life and my two previous roles I evangalized &lt;a href="https://azure.microsoft.com/en-us/services/devops/"&gt;Azure DevOps&lt;/a&gt; relentlessly. In one position we had nothing in terms of a tooling outside of a very outdated implementation of &lt;a href="https://subversion.apache.org/"&gt;SubVersion&lt;/a&gt;. In the other postion, add outdated versions of &lt;a href="https://www.atlassian.com/software/jira"&gt;Jira &lt;/a&gt;and &lt;a href="https://jenkins.io/"&gt;Jenkins&lt;/a&gt; to the mix. At least in this instance, Jenkins and SubVersion were somewhat connected, however, Jira was left out in the cold.&lt;/p&gt;

&lt;p&gt;Anytime I would demo Azure DevOps I faced the same questions and criticisms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;“But it won’t work with [insert non-Microsoft language], will it?”&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“It’s Microsoft, it will never work outside of their ecosystem!”&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“We want to host our applications with Amazon, this won’t work here.”&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those were the comments on the friendlier side of things. At times, they were just plain unprofessional (to put it nicely). But as &lt;a href="https://twitter.com/DonovanBrown?s=20"&gt;@DonovanBrown&lt;/a&gt;, “This ain’t your daddy’s Microsoft.”&lt;/p&gt;

&lt;p&gt;In this post I will show you how easy it is to deploy a .NET Core application to &lt;a href="https://aws.amazon.com/elasticbeanstalk/"&gt;AWS Elastic Beanstalk&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up the AWS Environment
&lt;/h3&gt;

&lt;p&gt;To begin, you will need to set up an AWS Elastic Beanstalk environment. Before writing this post, I had never touched AWS. The process is fairly straight forward.&lt;/p&gt;

&lt;p&gt;Login to the AWS Management Console and then search for “Elastic Beanstalk”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mBNG7oge--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/001-AWS-Management-Console.png%3Ffit%3D843%252C628%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mBNG7oge--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/001-AWS-Management-Console.png%3Ffit%3D843%252C628%26ssl%3D1" alt=""&gt;&lt;/a&gt;Click the &lt;strong&gt;Create New Application&lt;/strong&gt; button towards the upper right-hand corner. The below modal will pop up. Enter a name for your application in the &lt;strong&gt;Application name&lt;/strong&gt; field and click the &lt;strong&gt;Create&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---BIDSqU0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/002-Create-Web-App.png%3Ffit%3D843%252C384%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---BIDSqU0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/002-Create-Web-App.png%3Ffit%3D843%252C384%26ssl%3D1" alt=""&gt;&lt;/a&gt;This takes us to the Base configuration section. I selected &lt;strong&gt;.NET (Windows/IIS)&lt;/strong&gt; for the &lt;strong&gt;Platform&lt;/strong&gt;, however you could run .NET Core most of the Linux offerings. For &lt;strong&gt;Application code&lt;/strong&gt;, choose &lt;strong&gt;Sample application&lt;/strong&gt; as we will deploy our actual code with Azure DevOps. Once you have set the base configuration click the &lt;strong&gt;Create application&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rNn23b3f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/003-Create-Web-App-Configuration.png%3Ffit%3D843%252C382%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rNn23b3f--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/003-Create-Web-App-Configuration.png%3Ffit%3D843%252C382%26ssl%3D1" alt=""&gt;&lt;/a&gt;This will kickoff the creation of our Elastic Beanstalk environment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Dzw2UQe7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/01/004-Create-Web-App-Creating.png%3Ffit%3D843%252C271%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Dzw2UQe7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/01/004-Create-Web-App-Creating.png%3Ffit%3D843%252C271%26ssl%3D1" alt=""&gt;&lt;/a&gt;Once the creation is complete it will redirect you to the Elastic Beanstalk dashboard shown below. You can view the application by clicking the &lt;strong&gt;URL&lt;/strong&gt; at the top of the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VfroUQOG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/005-Create-Web-App-Created-1.png%3Ffit%3D843%252C342%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VfroUQOG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/005-Create-Web-App-Created-1.png%3Ffit%3D843%252C342%26ssl%3D1" alt=""&gt;&lt;/a&gt;Congratulations, you have just created a deault ASP.NET Web App.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hJ56Bu7c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/01/006-Create-Web-App-Created-Default-App.png%3Ffit%3D843%252C338%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hJ56Bu7c--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/01/006-Create-Web-App-Created-Default-App.png%3Ffit%3D843%252C338%26ssl%3D1" alt=""&gt;&lt;/a&gt;### Creating an AWS IAM Account&lt;/p&gt;

&lt;p&gt;Next, you will create an account that you can use to deploy your application from Azure DevOps. Click the dropdown menu associated with your &lt;strong&gt;account name&lt;/strong&gt; and then select &lt;strong&gt;My Security Credentials&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wv9X3hp---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/023-AWS-My-Security-Credentials-1.png%3Ffit%3D843%252C231%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wv9X3hp---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/023-AWS-My-Security-Credentials-1.png%3Ffit%3D843%252C231%26ssl%3D1" alt=""&gt;&lt;/a&gt;A warning modal will popup, select &lt;strong&gt;Get Started with IAM Users&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CtoqfbZJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/024-AWS-My-Security-Credentials-IAM-Warning-1.png%3Ffit%3D843%252C351%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CtoqfbZJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/024-AWS-My-Security-Credentials-IAM-Warning-1.png%3Ffit%3D843%252C351%26ssl%3D1" alt=""&gt;&lt;/a&gt;Next, you will create a security group. Enter “AzureDevOps” for the &lt;strong&gt;Group Name&lt;/strong&gt;. Then click &lt;strong&gt;Next Step&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9f6AL9Pz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/025-AWS-My-Security-Credentials-Create-Group.png%3Ffit%3D843%252C497%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9f6AL9Pz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/025-AWS-My-Security-Credentials-Create-Group.png%3Ffit%3D843%252C497%26ssl%3D1" alt=""&gt;&lt;/a&gt;This brings you to the Attach Policy page. Enter “AWSElasticBeanstalk” into the search box to filter through the policies. Then select &lt;strong&gt;AWSElasticBeanstalkFullAccess&lt;/strong&gt;. Click &lt;strong&gt;Attach Policy&lt;/strong&gt; to continue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VXCBAowK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/026-AWS-My-Security-Credentials-Attach-Policy.png%3Ffit%3D843%252C440%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VXCBAowK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/026-AWS-My-Security-Credentials-Attach-Policy.png%3Ffit%3D843%252C440%26ssl%3D1" alt=""&gt;&lt;/a&gt;Review the settings and click &lt;strong&gt;Create Group&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g_BkcmG7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/01/027-AWS-My-Security-Credentials-Review.png%3Ffit%3D843%252C194%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g_BkcmG7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/01/027-AWS-My-Security-Credentials-Review.png%3Ffit%3D843%252C194%26ssl%3D1" alt=""&gt;&lt;/a&gt;This brings you to the Add user screen. Enter a &lt;strong&gt;User name&lt;/strong&gt;. I used the name “AzureDevOps” for this example. For &lt;strong&gt;Acces type&lt;/strong&gt; select &lt;strong&gt;Programmatic access&lt;/strong&gt;. Then click &lt;strong&gt;Next: Permissions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s3ZTti8X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/028-AWS-My-Security-Credentials-Add-User.png%3Ffit%3D843%252C397%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s3ZTti8X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/028-AWS-My-Security-Credentials-Add-User.png%3Ffit%3D843%252C397%26ssl%3D1" alt=""&gt;&lt;/a&gt;Next we need to set the permissions on the account. We do this by adding the users to groups with policies attached to them. We will place this account in the AzureDevOps group we previously created. Click &lt;strong&gt;Next: Tags&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E5mTZME0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/029-AWS-My-Security-Credentials-Add-User-Set-permissions.png%3Ffit%3D843%252C390%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E5mTZME0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/029-AWS-My-Security-Credentials-Add-User-Set-permissions.png%3Ffit%3D843%252C390%26ssl%3D1" alt=""&gt;&lt;/a&gt;This will bring us to the Tags page. In this example I did not add any tags. Click &lt;strong&gt;Next: Review&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hdjvpPja--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/030-AWS-My-Security-Credentials-Add-User-Add-Tags.png%3Ffit%3D843%252C291%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hdjvpPja--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/030-AWS-My-Security-Credentials-Add-User-Add-Tags.png%3Ffit%3D843%252C291%26ssl%3D1" alt=""&gt;&lt;/a&gt;Review your account settings and if they are correct click &lt;strong&gt;Create user&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BPaNDu5g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/031-AWS-My-Security-Credentials-Add-User-Review.png%3Ffit%3D843%252C470%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BPaNDu5g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/031-AWS-My-Security-Credentials-Add-User-Review.png%3Ffit%3D843%252C470%26ssl%3D1" alt=""&gt;&lt;/a&gt;Congratulations, you have successfully create a new user account. Next copy the &lt;strong&gt;Access key ID&lt;/strong&gt; and &lt;strong&gt;Secret access key&lt;/strong&gt; as we will be storing them in &lt;a href="https://azure.microsoft.com/en-us/services/key-vault/"&gt;Azure Key Vault&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X3AgUgP2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/032-AWS-My-Security-Credentials-Add-User-Success.png%3Ffit%3D843%252C352%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X3AgUgP2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/032-AWS-My-Security-Credentials-Add-User-Success.png%3Ffit%3D843%252C352%26ssl%3D1" alt=""&gt;&lt;/a&gt;### Creating Azure DevOps Service Connection for AWS&lt;/p&gt;

&lt;p&gt;The next step is to create a new &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&amp;amp;tabs=yaml"&gt;Service Connection&lt;/a&gt; within Azure DevOps for AWS. This connection will use the account we created in the previous step.&lt;/p&gt;

&lt;p&gt;First, go to the Projects Settings by clicking the Settings button towards the bottom of the left hand menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BcIMCimQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/01-Azure-DevOps-Project-Settings.png%3Fw%3D843%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BcIMCimQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/01-Azure-DevOps-Project-Settings.png%3Fw%3D843%26ssl%3D1" alt=""&gt;&lt;/a&gt;Then scroll down to &lt;strong&gt;Pipelines&lt;/strong&gt; and select &lt;strong&gt;Service connections&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PxwnJfml--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/02-Azure-DevOps-Select-Service-Connections.png%3Fw%3D843%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PxwnJfml--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/02-Azure-DevOps-Select-Service-Connections.png%3Fw%3D843%26ssl%3D1" alt=""&gt;&lt;/a&gt;Next, click &lt;strong&gt;Create service connection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c0H-W_5V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/021-Azure-DevOps-New-Release-Task-AWS-Elastic-Beanstalk-Deploy-Application-Authenticate-Service-Connection-1.png%3Ffit%3D843%252C314%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c0H-W_5V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/021-Azure-DevOps-New-Release-Task-AWS-Elastic-Beanstalk-Deploy-Application-Authenticate-Service-Connection-1.png%3Ffit%3D843%252C314%26ssl%3D1" alt=""&gt;&lt;/a&gt;Select &lt;strong&gt;AWS&lt;/strong&gt; and click &lt;strong&gt;Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F5qMGArv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/022-Azure-DevOps-New-Release-Task-AWS-Elastic-Beanstalk-Deploy-Application-Authenticate-New-AWS-Service-Connection-1.png%3Ffit%3D843%252C715%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F5qMGArv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/022-Azure-DevOps-New-Release-Task-AWS-Elastic-Beanstalk-Deploy-Application-Authenticate-New-AWS-Service-Connection-1.png%3Ffit%3D843%252C715%26ssl%3D1" alt=""&gt;&lt;/a&gt;Fill in the &lt;strong&gt;Access Key ID&lt;/strong&gt; and &lt;strong&gt;Secret Access Key&lt;/strong&gt; from the IAM User you created in AWS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bnFLIXKw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/02/033-Azure-DevOps-AWS-Service-Connection-Add-Keys.png%3Fw%3D843%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bnFLIXKw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/02/033-Azure-DevOps-AWS-Service-Connection-Add-Keys.png%3Fw%3D843%26ssl%3D1" alt=""&gt;&lt;/a&gt;Enter a &lt;strong&gt;Service connection name&lt;/strong&gt; and check &lt;strong&gt;Grant access permission to all pipelines&lt;/strong&gt;. Then click &lt;strong&gt;Save.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yA1HhiRI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/034-Azure-DevOps-AWS-Service-Connection-Add-Name-1.png%3Fw%3D843%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yA1HhiRI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/034-Azure-DevOps-AWS-Service-Connection-Add-Name-1.png%3Fw%3D843%26ssl%3D1" alt=""&gt;&lt;/a&gt;You have now created a new service connection.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zpIiEQEZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/035-Azure-DevOps-AWS-Service-Connection-Created-Service-Connection.png%3Ffit%3D843%252C216%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zpIiEQEZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2020/02/035-Azure-DevOps-AWS-Service-Connection-Created-Service-Connection.png%3Ffit%3D843%252C216%26ssl%3D1" alt=""&gt;&lt;/a&gt;### Creating the Application&lt;/p&gt;

&lt;p&gt;In this post we will deploy a fairly simple .NET Core Application. Create a new .NET Core MVC Application and make the following changes.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;Index.chtml file&lt;/strong&gt; replace the existing code with the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@{
    ViewData["Title"] = "Home Page";
}

&amp;lt;div class="text-center"&amp;gt;
    &amp;lt;h1 class="display-4"&amp;gt;Welcome&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;This demo is running .NET Core on @ViewData["CloudEnv"].&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;Learn about &amp;lt;a href="https://docs.microsoft.com/aspnet/core"&amp;gt;building Web apps with ASP.NET Core&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then in the &lt;strong&gt;HomeController&lt;/strong&gt; add an &lt;strong&gt;IConfiguration property&lt;/strong&gt; and add it to the constructor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private readonly ILogger&amp;lt;HomeController&amp;gt; _logger;

private readonly IConfiguration _configuration;

public HomeController(ILogger&amp;lt;HomeController&amp;gt; logger, IConfiguration configuration)
{
     _logger = logger;
     _configuration = configuration;
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Replace the &lt;strong&gt;Index&lt;/strong&gt; method of the &lt;strong&gt;HomeController&lt;/strong&gt; with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```&lt;/p&gt;
&lt;pre class="wp-block-preformatted"&gt;public IActionResult Index()&lt;br&gt;
{&lt;br&gt;
     ViewData["CloudEnv"] = _configuration["CloudEnv"];&lt;br&gt;
     return View();&lt;br&gt;
}
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Add the setting **CloudEnv** to appsettings.json. Not the prefix and postfix “\_\_” in the CloudEnv variable. We are tokenizing this variable so that the value can be replaced with a pipeline variable later on.



```&amp;lt;pre class="wp-block-preformatted"&amp;gt;{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "CloudEnv":  "__CloudEnv__"
}
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Afterwards, add the &lt;strong&gt;CloudEnv&lt;/strong&gt; setting to appsettings.Development.json&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```&lt;/p&gt;&lt;pre class="wp-block-preformatted"&gt;{&lt;br&gt;
  "Logging": {&lt;br&gt;
    "LogLevel": {&lt;br&gt;
      "Default": "Information",&lt;br&gt;
      "Microsoft": "Warning",&lt;br&gt;
      "Microsoft.Hosting.Lifetime": "Information"&lt;br&gt;
    }&lt;br&gt;
  },&lt;br&gt;
  "CloudEnv": "my local machine"&lt;br&gt;
}
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

That’s it for the application. Now on to the pipeline.

### Creating the Multi-Stage Pipeline

In this example we will create a multi-stage pipeline using YAML. The first stage will build the pipeline and the second will deploy it to AWS.

### Marketplace Extensions

The multi-stage pipeline we are creating requires two [Marketplace Extensions](https://docs.microsoft.com/en-us/azure/devops/marketplace-extensibility/?view=azure-devops). Install the following extensions into your project. Consult [this reference guide](https://docs.microsoft.com/en-us/azure/devops/marketplace/install-extension?view=azure-devops&amp;amp;tabs=browser) if you are having issues installing the extensions.

- [Replace Tokens](https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens)
- [AWS Tools for Microsoft Visual Studio Team Services](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-vsts-tools)

### Creating the Initial Pipeline

First, we will create the basis for the pipeline.

In Azure DevOps select the **“Rocket Ship”** from the lefthand menu, then select **Pipelines**.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/01-Go-to-Pipelines.png?w=843&amp;amp;ssl=1)Next, select **New pipeline**

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/02-Select-New-Pipeline.png?fit=843%2C81&amp;amp;ssl=1)Then select where your code is located. For this example my code is stored in Azure Repos Git. However, your code could be in GitHub or any other source control tool.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/03-Select-Code-Location.png?w=843&amp;amp;ssl=1)Afterwards, select your repository.

![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/04-Select-Repository.png?w=843&amp;amp;ssl=1)Then select **Starter pipeline**. This will create a basic pipeline with a couple of tasks.

![](https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/05-Select-Starter-Pipeline.png?w=843&amp;amp;ssl=1)### Modifying the YAML

Before we begin, I would like to warn you that YAML can be very fickle and whitespaces matter. One misaligned space can cause the entire pipeline to fail.

To learn more about YAML pipelines [this reference guide](https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&amp;amp;tabs=schema#pipeline-structure) is an excellent resource.

### Initial Pipeline

The initial portion of our pipeline defines the trigger, which is defined as the master branch of our repository. Anytime master changes, our pipeline will automatically run.



```&amp;lt;pre class="wp-block-preformatted"&amp;gt;trigger:
- master
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;
  
  
  Stage 1 – Build
&lt;/h3&gt;

&lt;p&gt;The first stage of our pipeline downloads any dependencies and then builds the solution. It then creates then publishes the artifact that will be used in the second stage.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```&lt;/p&gt;&lt;pre class="wp-block-preformatted"&gt;stages:

&lt;/pre&gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;stage: 'Build'&lt;br&gt;
variables:&lt;br&gt;
solution: '*&lt;em&gt;/&lt;/em&gt;.sln'&lt;br&gt;
buildPlatform: 'Any CPU'&lt;br&gt;
buildConfiguration: 'Release'&lt;br&gt;
displayName: 'Build'&lt;br&gt;
jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;job: 'Build'
displayName: 'Build'
pool:
vmImage: 'windows-latest' 
steps:&lt;/li&gt;
&lt;li&gt;task: NuGetToolInstaller@1&lt;/li&gt;
&lt;li&gt;task: NuGetCommand@2
inputs:
restoreSolution: '$(solution)'&lt;/li&gt;
&lt;li&gt;task: VSBuild@1
inputs:
solution: '$(solution)'
msbuildArgs: '/p:SkipInvalidConfigurations=true /p:DeployOnBuild=true /p:WebPublishMethod=FileSystem /p:publishUrl="$(build.artifactstagingdirectory)\" /p:DeployDefaultTarget=WebPublish'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'&lt;/li&gt;
&lt;li&gt;task: VSTest@2
inputs:
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'&lt;/li&gt;
&lt;li&gt;task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
```
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: When deploying a .NET Core App to AWS Elastic Beanstalk you cannot use the default &lt;strong&gt;msbuildArgs&lt;/strong&gt;. For some reason Elastic Beanstalk cannot find the web.config file when the artifact is published as a zip file. Please note the &lt;strong&gt;WebPublishMethod=FileSystem&lt;/strong&gt; argument.&lt;/p&gt;
&lt;h3&gt;
  
  
  Stage 2 – Deploying to AWS
&lt;/h3&gt;

&lt;p&gt;The second stage of the pipeline deploys the artifact from the first stage to AWS.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```&lt;/p&gt;
&lt;pre class="wp-block-preformatted"&gt;  - stage: 'Deploy'&lt;br&gt;
  displayName: 'Deploy'&lt;br&gt;
  jobs:&lt;br&gt;
  - deployment: DeployToAws&lt;br&gt;
    displayName: 'Deploy To AWS Elastic Beanstalk'&lt;br&gt;
    pool:&lt;br&gt;
      vmImage: 'windows-latest'&lt;br&gt;
    variables:&lt;br&gt;
      CloudEnv: 'AWS Elastic Beanstalk'&lt;br&gt;
    environment: 'Production'&lt;br&gt;
    strategy:&lt;br&gt;
      runOnce:&lt;br&gt;
        deploy:&lt;br&gt;
          steps:&lt;br&gt;
          - download: current&lt;br&gt;
            artifact: drop&lt;br&gt;
          - task: replacetokens@3&lt;br&gt;
            displayName: 'Replace Tokens'&lt;br&gt;
            inputs:&lt;br&gt;
              rootDirectory: '$(Pipeline.Workspace)/drop'&lt;br&gt;
              targetFiles: '*&lt;em&gt;/&lt;/em&gt;.json'&lt;br&gt;
              tokenPrefix: '&lt;strong&gt;'&lt;br&gt;
              tokenSuffix: '&lt;/strong&gt;'

&lt;p&gt;          - task: BeanstalkDeployApplication@1&lt;br&gt;
            displayName: 'Deploy to Elastic Beanstalk'&lt;br&gt;
            inputs:&lt;br&gt;
              awsCredentials: 'AWS Deployment Service Connection'&lt;br&gt;
              regionName: 'us-east-2'&lt;br&gt;
              applicationName: DotNetCoreDemo&lt;br&gt;
              environmentName: 'Dotnetcoredemo-env'&lt;br&gt;
              applicationType: aspnetCoreWindows&lt;br&gt;
              dotnetPublishPath: '$(Pipeline.Workspace)/drop' &lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Notice in the deployment job we are setting a job level variable **CloudEnv**. We will use variable in a later task to replace our tokenized appsetting from earlier.



```&amp;lt;pre class="wp-block-preformatted"&amp;gt; - deployment: DeployToAws
     displayName: 'Deploy To AWS Elastic Beanstalk'
     pool:
       vmImage: 'windows-latest'
     variables:
       CloudEnv: 'AWS Elastic Beanstalk' 
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Let’s break down the tasks in this stage of the pipeline.&lt;/p&gt;

&lt;p&gt;The first task, &lt;strong&gt;download&lt;/strong&gt;, downloads the artifact we created in the previous build stage.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```&lt;/p&gt;&lt;pre class="wp-block-preformatted"&gt; - download: current&lt;br&gt;
             artifact: drop 
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

The next task, **replacetokens@3**, is the first Marketplace Extension we installed earlier. This tasks looks for any string with a prefix and suffix of “\_\_” and attempts to replace them with a variable defined in the pipeline. In this example, “AWS Elastic Beanstalk” replaces the value for CloudEnv in appsettings.json



```&amp;lt;pre class="wp-block-preformatted"&amp;gt; - task: replacetokens@3
            displayName: 'Replace Tokens'
            inputs:
              rootDirectory: '$(Pipeline.Workspace)/drop'
              targetFiles: '**/*.json'
              tokenPrefix: '__'
              tokenSuffix: '__' 
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;: The variable name must match the string without the prefix and suffix.&lt;/p&gt;

&lt;p&gt;The final task, &lt;strong&gt;BeanstalkDeployApplication@1&lt;/strong&gt;, is the second Marketplace Extension we installed earlier. This task deploys the artifact to AWS Elastic Beanstalk. Pay close attention to the inputs. The &lt;strong&gt;awsCredentials&lt;/strong&gt; argument is the name of the service connection we created earlier. &lt;strong&gt;regionName&lt;/strong&gt;, &lt;strong&gt;applicationName&lt;/strong&gt;, and &lt;strong&gt;environmentName&lt;/strong&gt; should match with the Elastic Beanstalk environment and application we originally created. The last setting, &lt;strong&gt;dotnetPublishPath&lt;/strong&gt;, is the location of the artifact we downloaded.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```&lt;/p&gt;&lt;pre class="wp-block-preformatted"&gt; - task: BeanstalkDeployApplication@1&lt;br&gt;
            displayName: 'Deploy to Elastic Beanstalk'&lt;br&gt;
            inputs:&lt;br&gt;
              awsCredentials: 'AWS Deployment Service Connection'&lt;br&gt;
              regionName: 'us-east-2'&lt;br&gt;
              applicationName: DotNetCoreDemo&lt;br&gt;
              environmentName: 'Dotnetcoredemo-env'&lt;br&gt;
              applicationType: aspnetCoreWindows&lt;br&gt;
              dotnetPublishPath: '$(Pipeline.Workspace)/drop' 
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

### The Entire Pipeline

Below is the complete multi-stage pipeline.



```&amp;lt;pre class="wp-block-preformatted"&amp;gt; trigger:
- master

stages:
- stage: 'Build'
  variables:
    solution: '**/*.sln'
    buildPlatform: 'Any CPU'
    buildConfiguration: 'Release'
  displayName: 'Build'
  jobs:
  - job: 'Build'
    displayName: 'Build'
    pool:  
      vmImage: 'windows-latest' 
    steps:
    - task: NuGetToolInstaller@1

    - task: NuGetCommand@2
      inputs:
        restoreSolution: '$(solution)'

    - task: VSBuild@1
      inputs:
        solution: '$(solution)'
        msbuildArgs: '/p:SkipInvalidConfigurations=true /p:DeployOnBuild=true /p:WebPublishMethod=FileSystem /p:publishUrl="$(build.artifactstagingdirectory)\\" /p:DeployDefaultTarget=WebPublish'
        platform: '$(buildPlatform)'
        configuration: '$(buildConfiguration)'

    - task: VSTest@2
      inputs:
        platform: '$(buildPlatform)'
        configuration: '$(buildConfiguration)'

    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'drop'
        publishLocation: 'Container'

- stage: 'Deploy'
  displayName: 'Deploy'
  jobs:
  - deployment: DeployToAws
    displayName: 'Deploy To AWS Elastic Beanstalk'
    pool:
      vmImage: 'windows-latest'
    variables:
      CloudEnv: 'AWS Elastic Beanstalk'
    environment: 'Production'
    strategy:
      runOnce:
        deploy:
          steps:
          - download: current
            artifact: drop
          - task: replacetokens@3
            displayName: 'Replace Tokens'
            inputs:
              rootDirectory: '$(Pipeline.Workspace)/drop'
              targetFiles: '**/*.json'
              tokenPrefix: '__'
              tokenSuffix: '__'

          - task: BeanstalkDeployApplication@1
            displayName: 'Deploy to Elastic Beanstalk'
            inputs:
              awsCredentials: 'AWS Deployment Service Connection'
              regionName: 'us-east-2'
              applicationName: DotNetCoreDemo
              environmentName: 'Dotnetcoredemo-env'
              applicationType: aspnetCoreWindows
              dotnetPublishPath: '$(Pipeline.Workspace)/drop' 
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;At this point we can run the pipeline and it will deploy our application to our AWS Elastic Beanstalk environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Successful Deployment
&lt;/h3&gt;

&lt;p&gt;As you can tell by the below screenshot the deployment was successful and the CloudEnv appsetting was replaced with the pipeline variable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zRee35Gt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/9999-Success-2.png%3Ffit%3D843%252C226%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zRee35Gt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/02/9999-Success-2.png%3Ffit%3D843%252C226%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;/pre&gt;&lt;/pre&gt;&lt;/pre&gt;

</description>
      <category>aws</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>Reap What You Sow II </title>
      <dc:creator>Napoleon 'Ike' Jones</dc:creator>
      <pubDate>Sat, 02 Nov 2019 03:56:04 +0000</pubDate>
      <link>https://dev.to/ikethedev/reap-what-you-sow-ii-1eh4</link>
      <guid>https://dev.to/ikethedev/reap-what-you-sow-ii-1eh4</guid>
      <description>&lt;p&gt;Reap What You Sow II&lt;br&gt;
IaC, Terraform, &amp;amp; Azure DevOps&lt;br&gt;
Now With YAML&lt;/p&gt;

&lt;p&gt;In this post I will take you through deploying your Infrastructure using Terraform and Azure DevOps. Instead of using Azure DevOps’ classic pipeline editor as I did in my previous post, &lt;a href="https://ikethe.dev/reap-what-you-sow/?preview_id=284&amp;amp;preview_nonce=e6dcceece9&amp;amp;preview=true&amp;amp;_thumbnail_id=285"&gt;Reap What You Sow&lt;/a&gt;, I will now use YAML.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Use YAML?
&lt;/h3&gt;

&lt;p&gt;For one, we should be treating &lt;strong&gt;Everything as Code&lt;/strong&gt;. Well at least everything we are able to. Treating things as code allows us to apply modern software engineering practices to them such as source control and a peer review process. Two, I like to learn new things. Three, I was &lt;a href="https://devblogs.microsoft.com/devops/top-stories-from-the-microsoft-devops-community-2019-07-26/"&gt;challenged&lt;/a&gt; to do it so I’m required by law to do it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Fear and Loathing in DevOps…
&lt;/h3&gt;

&lt;p&gt;Prior to accepting this challenge, I had absolutely no experience with YAML. I had looked at some pipelines written in YAML at one point and I was honestly intimidated. How terrified was I? The day I accepted the challenge, I had been attending &lt;a href="https://devopsdays.org/events/2019-indianapolis/welcome/"&gt;DevOps Days Indy&lt;/a&gt; and I explicitly told a few people about how I had been avoiding writing my pipelines in YAML. Little did I know I would have my first pipeline written in YAML up and running later that evening.&lt;/p&gt;
&lt;h3&gt;
  
  
  What is YAML?
&lt;/h3&gt;

&lt;p&gt;YAML Ain’t Markup Language (YAML) is a data serialization language that is commonly used for configuration files. You can read more about YAML and its history &lt;a href="https://en.wikipedia.org/wiki/YAML"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Let’s Get Started
&lt;/h3&gt;

&lt;p&gt;In order to follow along this post you will need to take a look at my previous &lt;a href="https://ikethe.dev/reap-what-you-sow/?preview_id=284&amp;amp;preview_nonce=e6dcceece9&amp;amp;preview=true&amp;amp;_thumbnail_id=285"&gt;post&lt;/a&gt;. At a minimum complete the steps prior to “Back to Azure DevOps.” It is, however, highly recommend that you complete it entirely as you will be able to click the “View YAML” button if you get stuck.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0wQZtlW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/View-YAML.png%3Fresize%3D843%252C250%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0wQZtlW7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/View-YAML.png%3Fresize%3D843%252C250%26ssl%3D1" alt=""&gt;&lt;/a&gt;### Enable Multi-stage Pipelines&lt;/p&gt;

&lt;p&gt;Once you have completed the above steps, make sure you have the new multi-stage pipeline feature enabled. Click on your profile in the upper right corner, then select Preview features. Make sure the Multi-stage pipelines feature is turned on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VVsxZ-OT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Preview-Feature-Multi-Stage-YAML.png%3Fw%3D843%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VVsxZ-OT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Preview-Feature-Multi-Stage-YAML.png%3Fw%3D843%26ssl%3D1" alt=""&gt;&lt;/a&gt;### Creating Your First YAML Pipeline&lt;/p&gt;

&lt;p&gt;Next, click Pipelines in the left-hand menu. Afterwards, click New Pipeline in the upper right corner of the Pipelines section. Then select your repository. If you’ve followed this blog, select Azure Repos Git.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FdX73ORr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/1-Create-New-Pipeline-Select-Repository.png%3Fresize%3D843%252C548%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FdX73ORr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/1-Create-New-Pipeline-Select-Repository.png%3Fresize%3D843%252C548%26ssl%3D1" alt=""&gt;&lt;/a&gt;Next, select Starter pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cb5839Bj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/2-Starter-Pipeline-1.png%3Fresize%3D843%252C431%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cb5839Bj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/2-Starter-Pipeline-1.png%3Fresize%3D843%252C431%26ssl%3D1" alt=""&gt;&lt;/a&gt;This will create a basic YAML pipeline, add it to our repository and merge it into the master branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--012e801X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/3-Starter-Pipeline-YAML.png%3Fresize%3D843%252C485%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--012e801X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/3-Starter-Pipeline-YAML.png%3Fresize%3D843%252C485%26ssl%3D1" alt=""&gt;&lt;/a&gt;The above YAML is fairly straight forward. It is triggered by the master branch (lines 6 and 7). Line 9 and 10 tells it what type of agent to use. While line 12 begins to describe the steps in the pipeline. This pipeline runs two inline scripts.&lt;/p&gt;

&lt;p&gt;You can learn more about Azure Pipelines YAML schema &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&amp;amp;tabs=schema"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next we are going update the YAML with our Build tasks. Open &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt; or your editor of choice. If you are using VS Code, download this &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azure-devops.azure-pipelines"&gt;Azure Pipelines&lt;/a&gt; extension. Next, git pull on master. You should now see a new file, azure-pipelines.yml.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1qwjW84I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/4-VS-Code-Starter-Pipeline.png%3Fw%3D843%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1qwjW84I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/4-VS-Code-Starter-Pipeline.png%3Fw%3D843%26ssl%3D1" alt=""&gt;&lt;/a&gt;For the purposes of this post, I worked directly on master. &lt;strong&gt;Do not do this in the real world!&lt;/strong&gt; In my first update of the pipeline, I removed the two existing steps. Then I made the following changes.&lt;/p&gt;

&lt;p&gt;To begin with, change the vmImage to ‘vs2017-win2016’ . &lt;/p&gt;

&lt;p&gt;Then add the CopyFile@2 task. TargetFolder is the only required input. Set it to ‘$(build.artifactstagingdirectory)/Terraform’. &lt;/p&gt;

&lt;p&gt;We also set the Contents to ‘**’ this specifies that we want to copy all files in the root directory and all files in the sub-folders. &lt;/p&gt;

&lt;p&gt;Reference documentation for the CopyFile@2 task is available &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/copy-files?view=azure-devops&amp;amp;tabs=yaml"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next add the PublishBuildArtifacts@1 task. Set the PathtoPublish to ‘$(build.artifactstagingdirectory)/Terraform’, the ArtifactName to ‘drop’.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I’m not sure why but the “code” block in WordPress adds a space to the first line of each YAML code snippet.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Starter pipeline
# Start with a minimal pipeline that you can customize to build and deploy your code.
# Add steps that build, run tests, deploy, and more:
# https://aka.ms/yaml

trigger:
- master

pool:
  vmImage: 'vs2017-win2016'

steps:
 - task: CopyFiles@2
   displayName: 'Copy Files'
   inputs:
     TargetFolder: '$(build.artifactstagingdirectory)/Terraform'
     Contents: '**'

 - task: PublishBuildArtifacts@1
   inputs:
   PathtoPublish: $(build.artifactstagingdirectory)/Terraform
   ArtifactName: drop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, git commit and push your code to master. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Congratulations you have a working build pipeline&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6D8_edBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/6-Successful-Build.png%3Fresize%3D843%252C445%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6D8_edBE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/6-Successful-Build.png%3Fresize%3D843%252C445%26ssl%3D1" alt=""&gt;&lt;/a&gt;### Time to Refactor&lt;/p&gt;

&lt;p&gt;Getting the build pipeline working was nice and all but what we really want is a full CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;The first step in this process is to refactor our existing pipeline into stages. Stages are logical boundaries in your pipeline such as Build, Deploy To Dev, Run Tests, Deploy to Prod, etc. You can read more about stages &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/stages?view=azure-devops&amp;amp;tabs=yaml"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The refactored pipeline looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trigger:
- master
stages:
- stage: Build  
  jobs:
  - job: Build
    pool:
      vmImage: 'vs2017-win2016'
    steps:
      - task: CopyFiles@2
        displayName: 'Copy Files'
        inputs:
          TargetFolder: '$(build.artifactstagingdirectory)/Terraform'
          Contents: '**'

      - task: PublishBuildArtifacts@1
        inputs:
          PathtoPublish: $(build.artifactstagingdirectory)/Terraform
          ArtifactName: drop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I added a stages block and then added the Build stage. In that Build stage, we add a jobs block with a single job named Build. From there, the rest of our code looks exactly the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the Deployment Functionality
&lt;/h3&gt;

&lt;p&gt;Since the goal is to convert the entire pipeline to YAML, its time to add the deployment functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- stage: Deploy
  jobs:
  - deployment: DeployInfrastructure
    pool:
      vmImage: 'vs2017-win2016'
    variables:
      functions_appinsights: puzzleplatesfunctionsappinsights
      functions_appservice: puzzleplatesfunctionsappservice
      functions_storage: puzzleplatesstorage
      location: centralus
      puzzleplates_functions: puzzleplatesfunctions
      resource_group: PuzzlePlates-ResourceGroup
      terraformstorageaccount: pzzlepltstfrmstrgaccnt
      terraformstoragerg: PuzzlePlatesStorage
    environment: 'Puzzel Plates Development Infrastructure'
    strategy:
      runOnce:
        deploy:

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;First, add a new stage named Deploy. Then add a jobs block and then add a deployment job. You can read more about deployment jobs &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/process/deployment-jobs?view=azure-devops"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Set the vmImage to ‘ vs2017-win2016’ .&lt;/p&gt;

&lt;p&gt;Then set your variables within the job. Variables can be set at either the job level or the root level.&lt;/p&gt;

&lt;p&gt;From there, set your environment name. Then declare the deployment strategy. For now, only runOnce is available.&lt;/p&gt;

&lt;p&gt;Next, define your deployment steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy:
  steps:
  - download: current
    artifact: drop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Continuing from deploy, the first step to define is downloading the build artifact. “-download” is short hand for the Download Pipeline Artifact task. You can read more about it &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/artifacts/pipeline-artifacts?view=azure-devops&amp;amp;tabs=yaml#artifacts-in-release-and-deployment-jobs"&gt;here&lt;/a&gt;. All we are doing is telling it to download the most current artifact named “drop”.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: AzureCLI@1
  displayName: 'Azure CLI - Create Terraform State Storage'
  inputs:
    azureSubscription: 'Terraform_Azure_RM'
    scriptLocation: inlineScript
    inlineScript: |
      call az group create --location centralus --name $(terraformstoragerg)

      call az storage account create --name $(terraformstorageaccount) --resource-group $(terraformstoragerg) --location centralus --sku Standard_LRS

       call az storage container create --name terraform --account-name $(terraformstorageaccount)

- task: AzurePowerShell@3
  inputs:
    name: 'Azure PowerShell - Get Key'
    azureSubscription: 'Terraform_Azure_RM'
    azurePowerShellVersion: LatestVersion
    scriptType: InlineScript
    Inline: |
      $key=(Get-AzureRmStorageAccountKey -ResourceGroupName $(terraformstoragerg) -AccountName $(terraformstorageaccount)).Value[0]
                Write-Host "##vso[task.setvariable variable=storagekey]$key"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, add the AzureCLI task. Set the displayName, then set the inputs. The first input, azureSubscription is set to a service principal that can create resources in Azure. Deploying infrastructure into Azure from Azure DevOps requires an Azure Resource Manager service connection. Follow these &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops"&gt;instructions&lt;/a&gt; to create one. After setting the subscription set the scriptLocation to inlineScript. Then set the inlineScrpt to the code shown above. This code creates the Azure Blob Storage to store Terraform’s State. The pipe symbol signifies that any indented text that follows should be treated as multi-line scalar value. You can read more about it on &lt;a href="https://stackoverflow.com/questions/15540635/what-is-the-use-of-the-pipe-symbol-in-yaml"&gt;stackoverflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Moving on, AzurePowerShell@3 task. &lt;strong&gt;Do not add the version 4 task as there are conflict with the inline script.&lt;/strong&gt; Set the name. Then set the azureSubscription to the principle service we used in the previous task. Afterwards set the azurePowerShellVersion to LatestVersion and scriptTYpe to InlineScript. Then copy the above code. This code saves the storage key into the release pipeline.&lt;/p&gt;

&lt;p&gt;Next we will be adding the replacetokens task. This task is a marketplace extension so you will need to install it into your Azure DevOps account. This task will replace all of the values with the “__” prefix and suffix with variables from the release pipeline. Click &lt;a href="https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens"&gt;here &lt;/a&gt;to get the extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Marketplace Extension
- task: replacetokens@3
  displayName: 'Replace Tokens'
  inputs:
    rootDirectory: '$(Pipeline.Workspace)/drop'
    targetFiles: '**/*.tf'
    tokenPrefix: '__'
    tokenSuffix: '__'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I had a little bit of trouble figuring out what the actual input arguments were named as they are slightly different that what is presented in the classic editor. I ended up having to dive into the extension’s code base on &lt;a href="https://github.com/qetza/vsts-replacetokens-task"&gt;GitHub&lt;/a&gt;. Set the rootDirectory to the drop directory in pipeline workspace. Then set the targetFiles to scan all .tf files in the current directory including all sub-directories.&lt;/p&gt;

&lt;p&gt;Next add the Terraform tasks. The Terraform task is another extension and you will need to install it into your Azure DevOps account. The extension can be found &lt;a href="https://marketplace.visualstudio.com/items?itemName=petergroenewegen.PeterGroenewegen-Xpirit-Vsts-Release-Terraform"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Terraform
- task: Terraform@2
  displayName: 'Terraform Init'
  inputs:
    TemplatePath: '$(Pipeline.Workspace)/drop'
    Arguments: init
    InstallTerraform: true
    UseAzureSub: true
    ConnectedServiceNameARM: 'Terraform_Azure_RM'

- task: Terraform@2
  displayName: 'Terraform Plan'
  inputs:
    TemplatePath: '$(Pipeline.Workspace)/drop'
    Arguments: plan
    InstallTerraform: true
    UseAzureSub: true
    ConnectedServiceNameARM: 'Terraform_Azure_RM'

- task: Terraform@2
  displayName: 'Terraform Apply'
  inputs:
    TemplatePath: '$(Pipeline.Workspace)/drop'
    Arguments: apply -auto-approve
    InstallTerraform: true
    UseAzureSub: true
    ConnectedServiceNameARM: 'Terraform_Azure_RM'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Terraform Init initializes a working directory containing Terraform configuration files on the agent. Set the TemplatePath to the artifact location, $(Pipline.Workspace)/drop. Next, set the Arguments to init. Then set InstallTerraform to true. Afterwards set UseAzureSub to true and set the ConnectedServiceNameARM to the service account you previously used.&lt;/p&gt;

&lt;p&gt;Terraform Plan compares existing infrastructure to what is in the code you are deploying and determines if it needs to create, update, or destroy resources. &lt;strong&gt;The settings are exactly the same except you set the Arguments to plan.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Terraform Apply applies the plan to the infrastructure. &lt;strong&gt;The settings are exactly the same as the two previous Terraform tasks except you set the Arguments to apply -auto-approve.&lt;/strong&gt; This final task will create, update, or destroy your infrastructure in Azure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Congratulations you now have a working multi-stage Azure Pipeline written in YAML!!!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Entire YAML Pipeline
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trigger:
- master
stages:
- stage: Build  
  jobs:
  - job: Build
    pool:
      vmImage: 'vs2017-win2016'
    steps:
      - task: CopyFiles@2
        displayName: 'Copy Files'
        inputs:
          TargetFolder: '$(build.artifactstagingdirectory)/Terraform'
          Contents: '**'

      - task: PublishBuildArtifacts@1
        inputs:
          PathtoPublish: $(build.artifactstagingdirectory)/Terraform
          ArtifactName: drop

- stage: Deploy
  jobs:
  - deployment: DeployInfrastructure
    pool:
      vmImage: 'vs2017-win2016'
    variables:
      functions_appinsights: puzzleplatesfunctionsappinsights
      functions_appservice: puzzleplatesfunctionsappservice
      functions_storage: puzzleplatesstorage
      location: centralus
      puzzleplates_functions: puzzleplatesfunctions
      resource_group: PuzzlePlates-ResourceGroup
      terraformstorageaccount: pzzlepltstfrmstrgaccnt
      terraformstoragerg: PuzzlePlatesStorage
    environment: 'Puzzel Plates Development Infrastructure'
    strategy:
      runOnce:
        deploy:
          steps:
          - download: current
            artifact: drop

          - task: AzureCLI@1
            displayName: 'Azure CLI - Create Terraform State Storage'
            inputs:
              azureSubscription: 'Terraform_Azure_RM'
              scriptLocation: inlineScript
              inlineScript: |
                call az group create --location centralus --name $(terraformstoragerg)

                call az storage account create --name $(terraformstorageaccount) --resource-group $(terraformstoragerg) --location centralus --sku Standard_LRS

                call az storage container create --name terraform --account-name $(terraformstorageaccount)

          - task: AzurePowerShell@3
            inputs:
              name: 'Azure PowerShell - Get Key'
              azureSubscription: 'Terraform_Azure_RM'
              azurePowerShellVersion: LatestVersion
              scriptType: InlineScript
              Inline: |
                $key=(Get-AzureRmStorageAccountKey -ResourceGroupName $(terraformstoragerg) -AccountName $(terraformstorageaccount)).Value[0]
                Write-Host "##vso[task.setvariable variable=storagekey]$key"

          #Marketplace Extension
          - task: replacetokens@3
            displayName: 'Replace Tokens'
            inputs:
              rootDirectory: '$(Pipeline.Workspace)/drop'
              targetFiles: '**/*.tf'
              tokenPrefix: '__'
              tokenSuffix: '__'

          #Terraform
          - task: Terraform@2
            displayName: 'Terraform Init'
            inputs:
              TemplatePath: '$(Pipeline.Workspace)/drop'
              Arguments: init
              InstallTerraform: true
              UseAzureSub: true
              ConnectedServiceNameARM: 'Terraform_Azure_RM'

          - task: Terraform@2
            displayName: 'Terraform Plan'
            inputs:
              TemplatePath: '$(Pipeline.Workspace)/drop'
              Arguments: plan
              InstallTerraform: true
              UseAzureSub: true
              ConnectedServiceNameARM: 'Terraform_Azure_RM'

          - task: Terraform@2
            displayName: 'Terraform Apply'
            inputs:
              TemplatePath: '$(Pipeline.Workspace)/drop'
              Arguments: apply -auto-approve
              InstallTerraform: true
              UseAzureSub: true
              ConnectedServiceNameARM: 'Terraform_Azure_RM'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Though at first I was intimidated I was able to get my first pipeline up and running within a few hours. &lt;/p&gt;

&lt;p&gt;I ran into some issues with spacing and indentation that took me longer than I care to admit. I also ran into an issue using the version 4 preview of the AzurePowershell task and downgraded to version 3.&lt;/p&gt;

&lt;p&gt;My initial fear was completely unfounded (except for indenting/spacing nightmares, those are completely justified). Once I had the initial pipeline built I was hooked. I thoroughly enjoyed writing the pipeline and am looking forward to the new it will offer.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>azure</category>
      <category>cicd</category>
      <category>yaml</category>
    </item>
    <item>
      <title>Reap What You Sow </title>
      <dc:creator>Napoleon 'Ike' Jones</dc:creator>
      <pubDate>Sat, 02 Nov 2019 03:50:13 +0000</pubDate>
      <link>https://dev.to/ikethedev/reap-what-you-sow-1ik5</link>
      <guid>https://dev.to/ikethedev/reap-what-you-sow-1ik5</guid>
      <description>&lt;p&gt;Reap What You Sow&lt;br&gt;
IaC, Terraform, &amp;amp; Azure DevOps&lt;/p&gt;
&lt;h3&gt;
  
  
  The Waiting Game
&lt;/h3&gt;

&lt;p&gt;In the past I have worked in environments that took ages to provision infrastructure. I’m not talking days here, I mean it took a minimum of six months – and that’s if you had a lot of political capital to spend. The majority of requests took about a year and required a minimum of twelve meetings and four, 30-page packets of paperwork that needed to be signed by multiple directors.&lt;/p&gt;

&lt;p&gt;After everything was approved, the virtual machines were manually provisioned to the minimal specifications. This was true even for development servers. Once provisioned, it was not unusual for the servers to be configured incorrectly. I have seen everything fat-fingered from RAM settings to incorrect time zones.&lt;/p&gt;

&lt;p&gt;Well, guess what? There is a much better way to provision infrastructure in modern environments.&lt;/p&gt;
&lt;h3&gt;
  
  
  Infrastructure as Code
&lt;/h3&gt;

&lt;p&gt;Infrastructure as Code or IaC is the process of provisioning and managing infrastructure through descriptive code that defines exactly what should be created, updated, or destroyed instead of using traditional manual configuration tools.&lt;/p&gt;

&lt;p&gt;By bringing current best practices of software engineering into the infrastruction world, IaC offers many benefits over the “old way” of doing things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Reduced Risk&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Increased Speed&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decreased Cost&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By removing the human factor from the provisioning process and putting the code in a repository, with a peer-review process, you begin to eliminate the mistakes that people are prone to make. Using the same code base and automated process to deploy your infrastructure – every time – increases consistency, while also increasing the speed of provisioning. This in turn frees up your engineers to focus on solving more complex problems, also known as reduced cost. Engineers aren’t cheap; so the less time they spend on menial work – the better.&lt;/p&gt;
&lt;h3&gt;
  
  
  Terraform
&lt;/h3&gt;

&lt;p&gt;In this post I will be using Terraform to provision and manage my infrastructure in Azure. Terraform is an open source tool for creating, updating, and versioning infrastructure safely and efficiently. It supports Microsoft Azure, Amazon Web Services, Google Cloud Platform, IBM Cloud, VMware vSphere and many others.&lt;/p&gt;
&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;Install the latest version of Terraform., &lt;a href="http://%20https://learn.hashicorp.com/terraform/getting-started/install.html"&gt;here&lt;/a&gt;.&lt;br&gt;
Install the latest version of the Azure CLI, &lt;a href="http://%20https://learn.hashicorp.com/terraform/getting-started/install.html"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once those are installed, make your way over to &lt;a href="https://dev.azure.com"&gt;Azure DevOps&lt;/a&gt;. This is where we will store our code and create our build/release pipeline. If you have never used Azure DevOps I highly recommend that you give it a try. I’m not going to go over Azure DevOps in detail as it is outside of the scope of this post.&lt;/p&gt;

&lt;p&gt;Deploying infrastructure into Azure from Azure DevOps requires an Azure Resource Manager service connection. Follow these &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops"&gt;instructions&lt;/a&gt; to create one.&lt;/p&gt;

&lt;p&gt;Create a new repository and select Terraform as the gitignore file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---4N_SgYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Create-Repository.png%3Fw%3D843%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---4N_SgYS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Create-Repository.png%3Fw%3D843%26ssl%3D1" alt=""&gt;&lt;/a&gt;Clone this repository to your computer and create a new file called main.tf. Write the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {

  required_version = "&amp;gt;= 0.11"

  backend "azurerm" {

    storage_account_name = "__terraformstorageaccount__"

    container_name = "terraform"

    key = "terraform.tfstate"

    access_key = "__storagekey__"

  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This code tells Terraform that it must use a version greater than or equal to 0.11, it is provisioning resources in Microsoft Azure, and storing state in Azure Blob Storage. You can read more about storing Terraform State in Azure &lt;a href="https://www.terraform.io/docs/backends/types/azurerm.html"&gt;here&lt;/a&gt;. If you notice some of the values have “__” as prefixes and postfixes it is because we are using those to identify variables we will store in our pipeline.&lt;/p&gt;

&lt;p&gt;Write the below code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_resource_group" "rg" {
  name     = "__resource_group__"
  location = "__location__"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This block of code tells Terraform to create a new resource group and we are defining the name and location via pipeline variables.&lt;/p&gt;

&lt;p&gt;Write the below code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_storage_account" "functions_storage" {
  name                     = "__functions_storage__"
  resource_group_name      = "${azurerm_resource_group.rg.name}"
  location                 = "${azurerm_resource_group.rg.location}"
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The above block tells Terraform to create a new storage account. If you look closely you will see that we are setting the resource_group_name and location a little differently. What we are doing is using configuration values from the previously created resource group to define configuration values on our storage account. We want everything in the same location to reduce network traffic and we want to use the resource group that we just created in the previous block of code. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The gist of this is pretty simple: you can access configuration values and output from created resource in other parts of your code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Write the below block of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_app_service_plan" "functions_appservice"{
  name                = "__functions_appservice__"
  location            = "${azurerm_resource_group.rg.location}"
  resource_group_name = "${azurerm_resource_group.rg.name}"
  kind                = "FunctionApp"

  sku {
    tier = "Dynamic"
    size = "Y1"
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We are now provisioning a new App Service Plan in Azure, which we are configuring with the sku for the consumption based plan since that is what Azure Functions requires to run.&lt;/p&gt;

&lt;p&gt;Write the below block of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_application_insights" "functions_appinsights"{
  name                = "__functions_appinsights__"
  location            = "${azurerm_resource_group.rg.location}"
  resource_group_name = "${azurerm_resource_group.rg.name}"
  application_type    = "Web"
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now we are provisioning application insights to provide metrics and error logging for our Azure Functions.&lt;/p&gt;

&lt;p&gt;Write the below block of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "azurerm_function_app" "function_app" {
  name                      = "__puzzleplates_functions__"
  location                  = "${azurerm_resource_group.rg.location}"
  resource_group_name       = "${azurerm_resource_group.rg.name}"
  app_service_plan_id       = "${azurerm_app_service_plan.functions_appservice.id}"
  storage_connection_string = "${azurerm_storage_account.functions_storage.primary_connection_string}"

  app_settings = {
    "APPINSIGHTS_INSTRUMENTATIONKEY" = "${azurerm_application_insights.functions_appinsights.instrumentation_key}"
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This section creates the actual Azure Functions App and adds the instrumentation key for the previously created AppInsights.&lt;/p&gt;

&lt;p&gt;Commit and Push your changes back to the remote repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Back To Azure DevOps
&lt;/h3&gt;

&lt;p&gt;In Azure DevOps create a new build pipeline and select “use the classic editor” at the bottom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xfCm1Yn_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Create-Build-Pipeline.png%3Fresize%3D843%252C689%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xfCm1Yn_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Create-Build-Pipeline.png%3Fresize%3D843%252C689%26ssl%3D1" alt=""&gt;&lt;/a&gt;Select your repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u6oMD8Aw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Select-Repository.png%3Fresize%3D843%252C535%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u6oMD8Aw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Select-Repository.png%3Fresize%3D843%252C535%26ssl%3D1" alt=""&gt;&lt;/a&gt;Select the Empty Job template at the top:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b5T4lkql--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Select-Empty-Job-Template.png%3Fresize%3D843%252C626%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b5T4lkql--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Select-Empty-Job-Template.png%3Fresize%3D843%252C626%26ssl%3D1" alt=""&gt;&lt;/a&gt;Add the Copy Files task to Agent job 1 with the same settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IBH_GYL0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Build-Copy-Files.png%3Fresize%3D843%252C300%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IBH_GYL0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Build-Copy-Files.png%3Fresize%3D843%252C300%26ssl%3D1" alt=""&gt;&lt;/a&gt;Add the Publish Artifact – drop task:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RFJr-I3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Build-Publish-Artifiact.png%3Fresize%3D843%252C299%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RFJr-I3G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Build-Publish-Artifiact.png%3Fresize%3D843%252C299%26ssl%3D1" alt=""&gt;&lt;/a&gt;Go to Triggers and check Enable Continuous Integration. With CI enabled, every code changed published to the master branch will trigger this build:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qBZEnj8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/Build-CI-Trigger.png%3Fresize%3D843%252C333%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qBZEnj8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/Build-CI-Trigger.png%3Fresize%3D843%252C333%26ssl%3D1" alt=""&gt;&lt;/a&gt;Hit Save &amp;amp; Queue. This will save your pipeline and kickoff a build. Once the build is successful, hit the Release button in the upper right corner:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PxMuyEqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Successful-Build.png%3Fresize%3D843%252C259%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PxMuyEqN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Successful-Build.png%3Fresize%3D843%252C259%26ssl%3D1" alt=""&gt;&lt;/a&gt;Select Empty job:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UuNV0y6j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/New-Release-Pipeline-Empty-Job.png%3Fresize%3D843%252C992%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UuNV0y6j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/New-Release-Pipeline-Empty-Job.png%3Fresize%3D843%252C992%26ssl%3D1" alt=""&gt;&lt;/a&gt;In Stage 1 select “1 job, 0 task” to edit the tasks in Stage 1 of the release pipeline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tnLP7Xmc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Release-Stage-1.png%3Fresize%3D843%252C481%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tnLP7Xmc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Release-Stage-1.png%3Fresize%3D843%252C481%26ssl%3D1" alt=""&gt;&lt;/a&gt;Add the Azure CLI task with the below settings. For the Azure Subscription make sure you use the service connection you created earlier:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kQwpjQRz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Azure-CLI.png%3Fresize%3D843%252C441%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kQwpjQRz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Azure-CLI.png%3Fresize%3D843%252C441%26ssl%3D1" alt=""&gt;&lt;/a&gt;Add the following code to the inline script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;call az group create --location centralus --name $(terraformstoragerg)

call az storage account create --name $(terraformstorageaccount) --resource-group $(terraformstoragerg) --location centralus --sku Standard_LRS

call az storage container create --name terraform --account-name $(terraformstorageaccount)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This code creates the Azure Blob Storage to store Terraform’s State.&lt;/p&gt;

&lt;p&gt;Afterwards add the Azure PowerShell task with the following settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0iGgsEzg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Azure-Powershell.png%3Fresize%3D843%252C405%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0iGgsEzg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Azure-Powershell.png%3Fresize%3D843%252C405%26ssl%3D1" alt=""&gt;&lt;/a&gt;Add the below code to the inline script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$key=(Get-AzureRmStorageAccountKey -ResourceGroupName $(terraformstoragerg) -AccountName $(terraformstorageaccount)).Value[0]

Write-Host "##vso[task.setvariable variable=storagekey]$key"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This code saves the storage key into the release pipeline.&lt;/p&gt;

&lt;p&gt;Add the Replace Tokens (installed from the Marketplace) to the pipeline. This task will replace all of the values with the “__” prefix and postfix with variables from the release pipeline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ujV7roa0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/Replace-Tokens.png%3Fresize%3D843%252C444%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ujV7roa0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/Replace-Tokens.png%3Fresize%3D843%252C444%26ssl%3D1" alt=""&gt;&lt;/a&gt;Then add three Run Terraform tasks: one for Init, one for Plan, and one for Apply. Terraform Init initializes Terraform on the agent. Terraform Plan compares existing infrastructure to what is in the code you are deploying and determines if it needs to create, update, or destroy resources. Terraform Apply applies the plan to the infrastructure.&lt;/p&gt;

&lt;p&gt;Terrraform Init:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w4rq0Vya--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/Terraform-Init.png%3Fresize%3D843%252C442%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w4rq0Vya--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/ikethe.dev/wp-content/uploads/2019/07/Terraform-Init.png%3Fresize%3D843%252C442%26ssl%3D1" alt=""&gt;&lt;/a&gt;Terraform Plan:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iLFwmR7C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Terraform-Plan.png%3Fresize%3D843%252C417%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iLFwmR7C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Terraform-Plan.png%3Fresize%3D843%252C417%26ssl%3D1" alt=""&gt;&lt;/a&gt;Terraform Apply:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dhcq4dW6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Terraform-Apply.png%3Fresize%3D843%252C457%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dhcq4dW6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Terraform-Apply.png%3Fresize%3D843%252C457%26ssl%3D1" alt=""&gt;&lt;/a&gt;Next you will have to create and set your release variables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XXceIB9H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Release-Variables.png%3Fresize%3D843%252C290%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XXceIB9H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Release-Variables.png%3Fresize%3D843%252C290%26ssl%3D1" alt=""&gt;&lt;/a&gt;Afterwards hit Save in the upper right corner:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VkTk9ATe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Release-Save-Tasks.png%3Fresize%3D843%252C290%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VkTk9ATe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Release-Save-Tasks.png%3Fresize%3D843%252C290%26ssl%3D1" alt=""&gt;&lt;/a&gt;Click on the Continuous Deployment Trigger (Lightning Bolt) under Artifacts. Then enable continuous deployment. Now every time your build creates a new artifact a release will deploy your infrastructure into Azure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7jEzMUt2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Release-Trigger.png%3Fresize%3D843%252C281%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7jEzMUt2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/ikethe.dev/wp-content/uploads/2019/07/Release-Trigger.png%3Fresize%3D843%252C281%26ssl%3D1" alt=""&gt;&lt;/a&gt;Click Save again and then click Create release to the right of the save button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mVq3wIDj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Create-Release-Button.png%3Fw%3D843%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mVq3wIDj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Create-Release-Button.png%3Fw%3D843%26ssl%3D1" alt=""&gt;&lt;/a&gt;This will begin your release.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cv7lyb1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Successful-Release.png%3Fresize%3D843%252C375%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cv7lyb1W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/ikethe.dev/wp-content/uploads/2019/07/Successful-Release.png%3Fresize%3D843%252C375%26ssl%3D1" alt=""&gt;&lt;/a&gt;If everything went according to plan, you should now have the following deployed in Azure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resource Group&lt;/li&gt;
&lt;li&gt;Storage Account&lt;/li&gt;
&lt;li&gt;App Service Plan&lt;/li&gt;
&lt;li&gt;Application Insights&lt;/li&gt;
&lt;li&gt;Azure Functions App&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>azure</category>
      <category>azuredevops</category>
      <category>devops</category>
      <category>iac</category>
    </item>
  </channel>
</rss>
