<?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: Paul Delcogliano</title>
    <description>The latest articles on DEV Community by Paul Delcogliano (@pdelcogliano).</description>
    <link>https://dev.to/pdelcogliano</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%2F280488%2F28e2862d-7b28-4fd8-93c0-11748cede487.jpg</url>
      <title>DEV Community: Paul Delcogliano</title>
      <link>https://dev.to/pdelcogliano</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pdelcogliano"/>
    <language>en</language>
    <item>
      <title>How to Use Terraform Conditional Expressions</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Tue, 14 Nov 2023 14:13:12 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/how-to-use-terraform-conditional-expressions-34g5</link>
      <guid>https://dev.to/pdelcogliano/how-to-use-terraform-conditional-expressions-34g5</guid>
      <description>&lt;p&gt;Conditional expressions are a key part of any programming language. Conditional expressions return a value based on whether an expression evaluates to true or false. In most modern languages, conditional expressions are represented by the if...else statement. Here is an example of a conditional expression: If this article is engaging, then people will continue reading it, else, no one will see it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a conditional expression in terraform
&lt;/h2&gt;

&lt;p&gt;Terraform doesn't offer the traditional if...else statement. Instead, it provides a ternary operator for conditional expressions. Conditional expressions in Terraform can be applied to myriad objects, including resources, data sources, outputs, and modules.&lt;/p&gt;

&lt;p&gt;Conditional expressions provide flexibility and re-usability to Terraform configurations. They allow configurations to adapt to different environments, requirements, or scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the Terraform ternary operator
&lt;/h3&gt;

&lt;p&gt;A ternary operator is one which operates on three operators. Syntactically, the ternary operator defines a Boolean condition, a value when the condition is true, and a value when the condition is false. &lt;/p&gt;

&lt;p&gt;The ternary operator in Terraform looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;true_part&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;false_part&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;condition&lt;/code&gt; operand is any expression whose value resolves to a Boolean, like article == engaging. &lt;code&gt;true_part&lt;/code&gt; is the value returned when the condition evaluates to true. &lt;code&gt;false_part&lt;/code&gt; is the value when the condition evaluates to false.&lt;/p&gt;

&lt;p&gt;Here is a basic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;account_tier&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"Standard"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Premium"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous ternary expression can be broken down like so:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;?&lt;/th&gt;
&lt;th&gt;true part&lt;/th&gt;
&lt;th&gt;:&lt;/th&gt;
&lt;th&gt;false part&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;If the &lt;code&gt;environment&lt;/code&gt; variable is equal to "dev"&lt;/td&gt;
&lt;td&gt;then&lt;/td&gt;
&lt;td&gt;assign the value "Standard" to the account_tier attribute&lt;/td&gt;
&lt;td&gt;else&lt;/td&gt;
&lt;td&gt;assign "Premium"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two result values, true_part, and false_part must both be the same data type, i.e., two strings. If the data types are different, terraform will attempt to convert them to a common type automatically. For example, terraform will automatically convert the result of the following expression to string since numbers can be converted to string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allow_public&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While automatic data type conversion is a nice convenience, it should not be relied upon as it leads to configurations that are confusing and can be error-prone. Instead, explicitly convert data types to avoid automatic data type conversion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allow_public&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tonumber&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example illustrates the point but admittedly is a bit contrived.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditional expression use cases
&lt;/h3&gt;

&lt;p&gt;A common use case for conditional expressions is to test for the existence of a variable's value and define a default value to replace invalid values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the value of var.environment is an empty string then set its value to "dev", otherwise use the actual value of var.environment.&lt;/p&gt;

&lt;p&gt;Conditional expressions are often used to configure settings differently based on certain conditions. In this example, a conditional expression configures an Azure storage account's access_tier attribute. If the var.environment value is "dev", the access tier will be set to "Cool". Otherwise, it will be "Hot".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"my_storage"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stmystorage"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-conditional-demo"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;                        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eastus"&lt;/span&gt;
  &lt;span class="nx"&gt;access_tier&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"Cool"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Hot"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Create a resource using a conditional expression
&lt;/h4&gt;

&lt;p&gt;By default, terraform creates one instance of a resource. Terraform's count meta-argument instructs terraform to create several similar objects without writing a separate block for each one. If a resource or module block includes a count argument with a whole number value, terraform creates that many instances of the resource. Setting count to zero results in no instances of the resource being created.&lt;/p&gt;

&lt;p&gt;When combined with a conditional expression, count can be used to create powerful logic to control whether to create a resource. The following example evaluates the value of the add_storage_account Boolean variable. If it is true, count will be assigned 1. When this happens, an Azure storage account will be created. However, if add_storage_account is false, the count will be zero and no storage account will be created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"add_storage_account"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"boolean to determine whether to create a storage account or not"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"my_storage_account"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_storage_account&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-conditional-demo"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eastus"&lt;/span&gt;
  &lt;span class="nx"&gt;account_tier&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard"&lt;/span&gt;
  &lt;span class="nx"&gt;account_replication_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LRS"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stspacelift&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="k"&gt;}${&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rand_suffix&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to count, terraform's for_each meta-argument is used to create many instances of the same resource. for_each works with a list of values to create resources with distinct arguments. The difference between the two meta-arguments is that count is best used when nearly identical resources need to be created. for_each is best for creating resources where some of the resources need distinct attribute values.&lt;/p&gt;

&lt;p&gt;A typical use case for the for_each argument is to use a map of objects to assign multiple users to a group. A conditional expression can be added to filter out resources that should be added to a group based on their user type. This example shows one way to do that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"users"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A list of users to add"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;user_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"member1"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"member1@abc.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;user_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Member"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"member2"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"member2@abc.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;user_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Member"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s2"&gt;"guest1"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"guest@abc.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;user_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Guest"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Get the users from AAD&lt;/span&gt;
&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"azuread_user"&lt;/span&gt; &lt;span class="s2"&gt;"my_users"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;


  &lt;span class="nx"&gt;user_principal_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azuread_group"&lt;/span&gt; &lt;span class="s2"&gt;"my_group"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;display_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"mygroup"&lt;/span&gt;
  &lt;span class="nx"&gt;security_enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Only add users who are members to the group&lt;/span&gt;
&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azuread_group_member"&lt;/span&gt; &lt;span class="s2"&gt;"my_group_members"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azuread_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_users&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Member"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;group_object_id&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azuread_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;member_object_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azuread_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_users&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The users variable defines an object map, with each object having a property named "email". Three user objects are added to the map, two members and one guest. A data source is used to retrieve users from AAD. The for_each argument in the azuread_group_member resource loops through the users returned from AAD and uses a condition to apply a filter for users who are members. Each user in the filtered results will be added to the group named "my_group".&lt;/p&gt;

&lt;h4&gt;
  
  
  Conditional expressions on other Terraform objects
&lt;/h4&gt;

&lt;p&gt;In addition to their application to resources, conditional expressions can be combined with count and for-each on the following terraform objects: module blocks, data sources, dynamic blocks, and local and/or output variables. The syntax is identical as shown for a resource block.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Object&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;module block&lt;/td&gt;
&lt;td&gt;control the creation and number of instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;data source&lt;/td&gt;
&lt;td&gt;reduce number of records via filter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dynamic block&lt;/td&gt;
&lt;td&gt;control the creation and number of instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;local variable&lt;/td&gt;
&lt;td&gt;set variable values based on conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;output variable&lt;/td&gt;
&lt;td&gt;return values based on conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here are some examples that use conditional expressions with &lt;code&gt;count&lt;/code&gt; and &lt;code&gt;for_each&lt;/code&gt; on a module block, a data source, a local variable, and an output variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;# module examples&lt;/span&gt;
&lt;span class="c1"&gt;# determine if an account should be created&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"storage"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_storage_account&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;


  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./path to module tf file"&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# filter list of users to add to a group&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"group_members"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azuread_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_users&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Member"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./path to module tf file"&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# data source example&lt;/span&gt;
&lt;span class="c1"&gt;# filter a data source using the `users` variable from above, looking for "members"&lt;/span&gt;
&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"azuread_user"&lt;/span&gt; &lt;span class="s2"&gt;"my_users"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
   &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="nx"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_type&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Member"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;user_principal_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# local variable example&lt;/span&gt;
&lt;span class="c1"&gt;# uses a conditional expression to assign a value to the "rand_suffix" variable if the `add_storage_account` variable is true&lt;/span&gt;
&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# "rand_suffix" can be appended to the storage account name.&lt;/span&gt;
    &lt;span class="nx"&gt;rand_suffix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_storage_account&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;random_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# output variable example&lt;/span&gt;
&lt;span class="c1"&gt;# return a storage account name, if an account was created. Empty string otherwise&lt;/span&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"storage_account_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_storage_account&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;my_storage_account&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multiple Conditions
&lt;/h3&gt;

&lt;p&gt;Complex logic can be created when conditional expressions are combined with Terraform's logical operators. Terraform provides the logical operators &amp;amp;&amp;amp; (AND), || (OR), and ! (NOT).&lt;/p&gt;

&lt;p&gt;This example combines two conditions using the and operator. In this case, if add_storage_account is true and environment equals "prod", two instances of the resource are created. Otherwise, none are created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_storage_account&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conditional logic can also be nested. For instance, the true_part or false_part of the ternary operator could be another conditional expression. Converting the previous example, but replacing the logical and with nested logic would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add_storage_account&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the &lt;em&gt;true_part&lt;/em&gt; is another condition, eg., does environment equal "prod". While the result is similar to the code using a logical &lt;em&gt;and&lt;/em&gt;, the nested version is a bit harder to read and not as clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping it up
&lt;/h3&gt;

&lt;p&gt;There are a few limitations to be aware of when using conditional expressions. While they can be applied to many object types, they cannot be applied to providers. count and for_each are mutually exclusive and cannot be used on the same object. Finally, while this won't affect many terraform implementations, it's important to note that module support was added for count and for_each in version 0.13. Both meta-arguments can be only applied to resource blocks in versions prior to 0.13.&lt;/p&gt;

&lt;p&gt;As with all software development, conditional expressions have a few best practices to follow. Remember to avoid complex conditions. Nested conditions, while possible, add complexity to the configuration making it difficult to maintain and comprehend. Descriptive variable names facilitate the readability of the configuration. Also, be sure to test each conditional expression to ensure it works as intended.&lt;/p&gt;

&lt;p&gt;Flexible configurations that adapt to different environments, requirements, and/or scenarios are possible with conditional expressions. Terraform's ternary operator is the main way to apply conditional logic. Ternary operators used on variables help set default and invalid values. Conditional expressions combined with count and for_each offer the ability to control whether a resource is created, and how many instances of a resource to create. They also allow for filtering data and configuring specific resource attributes.&lt;/p&gt;

&lt;p&gt;Conditional expressions are easy to learn and implement and are another essential tool in any IaC toolbox.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>iac</category>
    </item>
    <item>
      <title>Terraform vs. Pulumi : A Look at Both Tools</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Sat, 08 Jul 2023 21:07:54 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/terraform-vs-pulumi-a-look-at-both-tools-3idl</link>
      <guid>https://dev.to/pdelcogliano/terraform-vs-pulumi-a-look-at-both-tools-3idl</guid>
      <description>&lt;p&gt;Cloud-native applications embrace the widely accepted practice of Infrastructure as Code, or IaC. IaC applies software development practices to the management of infrastructure, i.e., networks, virtual machines, and load balancers, etc.&lt;/p&gt;

&lt;p&gt;Applying software development techniques to managing infrastructure provides several benefits, including versioning using a repository like GitHub, and streamlining provisioning and maintenance by creating repeatable processes. Code reviews and audits can be done to ensure changes to infrastructure are correct and won't introduce errors.&lt;/p&gt;

&lt;p&gt;With IaC, infrastructure becomes reproducible in a consistent manner, and can be included in a CI/CD deployment pipeline. IaC is scalable and helps to eliminate human error.&lt;/p&gt;

&lt;p&gt;Many IaC tools are available to facilitate the provisioning of cloud-native applications across all the major cloud platforms. Terraform and Pulumi are two such tools. In this article we'll take a look at both and provide a comparison between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.terraform.io" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;, is an open-source tool created by Hashicorp. It is probably the most well-known IaC infrastructure provisioning tool. Terraform is cloud-agnostic and allows automating infrastructure stacks from multiple cloud service providers simultaneously. Terraform uses its own domain-specific language called Hashicorp Configuration Language (HCL).&lt;/p&gt;

&lt;p&gt;Terraform uses "plans" to validate the configuration and display exactly what elements are going to change before the changes are applied. For a detailed look at plans, check out the &lt;a href="https://spacelift.io/blog/terraform-plan" rel="noopener noreferrer"&gt;intro to Terraform plans article&lt;/a&gt; on spacelift.io.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pulumi
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.pulumi.com/" rel="noopener noreferrer"&gt;Pulumi&lt;/a&gt; is an IaC tool that uses a declarative format to deploy infrastructure. Like Terraform, it is open source on GitHub and is free to use. It offers the ability to use any major development language in the creation of infrastructure template files. Pulumi, like Terraform, supports all of the major cloud providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Comparison Between Terraform and Pulumi
&lt;/h2&gt;

&lt;p&gt;Both tools are software that automate the management of cloud computing infrastructure using code and templates. They help businesses provision and configure servers, storage, databases, and networks without manual intervention or hardware controls.&lt;/p&gt;

&lt;p&gt;They store the documented configurations in version control systems that ensure consistency and reduce turnaround time. They support DevOps and NetOps practices, enabling agility, scalability, and resilience. They can be cloud-specific or cloud-agnostic, depending on the provider and the environment.&lt;/p&gt;

&lt;p&gt;The following table lists the major differences between each tool.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Terraform&lt;/th&gt;
&lt;th&gt;Pulumi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language Support&lt;/td&gt;
&lt;td&gt;Proprietary language - Hashicorp Configuration Language (HCL)&lt;/td&gt;
&lt;td&gt;Pulumi SDK supports many languages including TypeScript, JavaScript, Go, C#, F#, Java, YAML. Also provides Pulumi YAML, which is a configuration language used to describe infrastructure.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IDE Features&lt;/td&gt;
&lt;td&gt;Limited; through extensions&lt;/td&gt;
&lt;td&gt;Code completion, strong typing, error indicators, etc&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OSS License&lt;/td&gt;
&lt;td&gt;Mozilla Public License 2.0&lt;/td&gt;
&lt;td&gt;Apache License 2.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testability&lt;/td&gt;
&lt;td&gt;Integration testing via third party tools like &lt;a href="https://terratest.gruntwork.io/" rel="noopener noreferrer"&gt;Terratest&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Supports unit, property, and integration tests. Supports modern test frameworks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud Providers&lt;/td&gt;
&lt;td&gt;Multi-Provider Support&lt;/td&gt;
&lt;td&gt;Multi-Provider Support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State Mgmt&lt;/td&gt;
&lt;td&gt;Local state file by default; Cloud options available&lt;/td&gt;
&lt;td&gt;Remote state by default managed via Pulumi cloud; Local file available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets Mgmt&lt;/td&gt;
&lt;td&gt;Unencrypted in state file&lt;/td&gt;
&lt;td&gt;Encrypted in transit and state file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrastructure Reuse&lt;/td&gt;
&lt;td&gt;Can reuse Terraform modules&lt;/td&gt;
&lt;td&gt;Functions, classes, packages and Pulumi components can be reused&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's take a deeper look into each of the differences listed above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Language Support
&lt;/h3&gt;

&lt;p&gt;Terraform's HCL is JSON-compatible and is used to create the configuration files that describe the infrastructure resources in the stack. It uses proprietary syntax for common programming constructs like conditionals and loops. Terraform recently released the beta of a development kit that allows you to use programming languages that compile to HCL.&lt;/p&gt;

&lt;p&gt;Here's a look at a typical Terrafrom configuration file created using HCL to provision an Azure Resource Group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"=3.0.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Azure location for the resource group"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"iac"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-iac-example-centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"iac-test"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pulumi supports familiar programming languages like TypeScript, JavaScript, Python, Go, and C#, among others. This flexibility allows you write to your templates in the language you are most comfortable with. Pulumi will also convert HCL configurations into Pulumi templates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pulumi.com/docs/concepts/vs/terraform/terminology/" rel="noopener noreferrer"&gt;This reference&lt;/a&gt; provides a document listing Terraform terminology and command equivalents in Pulumi. A Pulumi template file written in C# to create the same Resource Group in Azure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Pulumi&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Azure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyStack&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MyStack&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;iac&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Azure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ResourceGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"iac"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Azure&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceGroupArgs&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"iac-test"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IDE Features
&lt;/h3&gt;

&lt;p&gt;Plugins for Terraform are available for some IDEs like VS Code. IDE integration is limited to the capabilities of the plugin itself.&lt;/p&gt;

&lt;p&gt;Pulumi's support for common programming languages means you get the rich IDE integration that comes with the language you choose. For example, when using C#, Visual Studio provides code completion, strong data types, intellisense, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Source Licenses
&lt;/h3&gt;

&lt;p&gt;Terraform uses the &lt;a href="https://www.mozilla.org/en-US/MPL/2.0/" rel="noopener noreferrer"&gt;Mozilla Public License 2.0&lt;/a&gt;. Pulumi uses the &lt;a href="https://www.apache.org/licenses/LICENSE-2.0.html" rel="noopener noreferrer"&gt;Apache License 2.0&lt;/a&gt;. There are differences between each licensing model. Your use case will determine the license that is appropriate for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testability
&lt;/h3&gt;

&lt;p&gt;Pulumi's support for common programming languages like C# allows you to perform unit tests on your IaC templates using .Net test tools, like xUnit. Pulumi also provides the ability to perform tests that mock external calls.&lt;/p&gt;

&lt;p&gt;Terraform supports integration testing. The &lt;code&gt;plan&lt;/code&gt; command shows the proposed infrastructure changes before they are applied, allowing you to review the changes prior to their execution. There are also commands to validate, and format a configuration file. Robust integration testing can be performed using third party tools like &lt;a href="https://terratest.gruntwork.io/" rel="noopener noreferrer"&gt;Terratest&lt;/a&gt;. Terratest is a Go library which enables you to write automated tests for infrastructure code. It provides many helper functions and patterns for common infrastructure testing tasks such as unit testing, integration testing, end-to-end testing, dependency injection, testing parallelism, retry logic, error handling, and static analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud Provider Support
&lt;/h3&gt;

&lt;p&gt;A resource provider handles communications with a cloud service to manage the infrastructure resources defined in IaC configurations.&lt;/p&gt;

&lt;p&gt;Both tools support all the major cloud services. Terraform has a community of developers writing custom providers for a myriad of services. Navigating their &lt;a href="https://registry.terraform.io/browse/providers" rel="noopener noreferrer"&gt;provider registry&lt;/a&gt; you'll find provider support for services like Ansible and Atlassian. New cloud services aren’t always available to deploy using Terraform on day one.&lt;/p&gt;

&lt;p&gt;Pulumi supports over 60 of the major cloud services. They also create "Native" providres for AWS, Azure, Google, and Kubernetes, which receive same-day support with every new release. The &lt;a href="https://www.pulumi.com/registry/" rel="noopener noreferrer"&gt;Pulumi registry&lt;/a&gt; provides detailed information for each provider they support. Also of note, Pulumi is able to adapt Terraform providers, so you can use the custom providers created by the Terraform community in your Pulumi templates.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Management
&lt;/h3&gt;

&lt;p&gt;IaC tools store metadata about your infrastructure so that it can manage your cloud resources. This metadata is called &lt;em&gt;state&lt;/em&gt;. Both Terraform and Pulumi offer a desired state model where the code represents the desired infrastructure state and the tool compares this desired state with the stack’s current state to determine the resources to be created, updated, or deleted.&lt;/p&gt;

&lt;p&gt;By default, Terraform stores state locally in a file named terraform.tfstate. Terraform state can be stored using a centrally located file, i.e. Azure blob container. This is the recommended configuration for the state file. Storing state in a centrally located file provides for backup and recovery scenarios, and allows larger teams to collaborate and manage infrastructure with shared state.&lt;/p&gt;

&lt;p&gt;In contrast, Pulumi stores it state in the Pulumi Cloud by default. Pulumi's use of common development languages allows state to be compared as a "diff", like how code is compared and reviewed across versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secrets Management
&lt;/h3&gt;

&lt;p&gt;The management of secrets, e.g., database credentials, API keys, etc., in code can become a security risk. Terraform manages secrets through a separate product named Vault. Vault enables the management secrets without the need for developers to have direct access to the secrets. Vault encrypts the values it stores. It can be deployed as a SaaS or run locally as a service. Variables marked as "secret" will be excluded from an output unless the &lt;code&gt;secrets&lt;/code&gt; command line arguement is provided.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="nx"&gt;secrets&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is important to note that Terraform state files store secrets in plain text. Because of this you should restrict access to the state file to only those with the appropriate level of authorization. A recommended best practice is to always encrypt your state file to prevent accidentally leaking sensitive information.&lt;/p&gt;

&lt;p&gt;As mentioned above, Pulumi stores secrets in state in the Pulumi Cloud. You can also choose your own provider to store your secrets. Pulumi always stores and transmits secrets securely. It also supports encrypting sensitive data for extra protection. Configuration settings can be encrypted via the CLI command &lt;code&gt;config set&lt;/code&gt; with the &lt;code&gt;--secret flag&lt;/code&gt;. Secrets can also be set during runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure Reuse
&lt;/h3&gt;

&lt;p&gt;A development best practice is to avoid duplicating code. The same practice applies to IaC. The ability to reuse configurations lowers the overall overhead involved with managing infrastructure.&lt;/p&gt;

&lt;p&gt;Terraform provides a library of reusable &lt;a href="https://registry.terraform.io/browse/modules" rel="noopener noreferrer"&gt;modules&lt;/a&gt;. A module is comprised of a collection of .tf and/or .tf.json files stored in the same folder. Modules are the main way to package and reuse resource configurations with Terraform. Modules can be referenced from a public online registry or your own local module library.&lt;/p&gt;

&lt;p&gt;Since Pulumi uses common programming languages, structures like classes, functions, and packages are reusable. Pulumi also provides a searchable &lt;a href="https://www.pulumi.com/registry/" rel="noopener noreferrer"&gt;registry&lt;/a&gt; where you can find packages that can be installed directly into your project. You can also create your own library of reusable packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform or Pulumi: Which Should You Choose?
&lt;/h2&gt;

&lt;p&gt;As in most decisions in software development, the answer is - it depends. Pulumi's native language support is its biggest advantage. By using native languages, you can embed IaC code directly within your application, perform unit testing, and reduce the learning curve by using a familiar language. This would be most important to developers who are new to DevOps. IT administrators with little coding experience will probably prefer the simplicity of HCL.&lt;/p&gt;

&lt;p&gt;Bottom line, if the prospect of having to learn a proprietary language like HCL scares you, or if you think you'll need to integrate your IaC deployments within your application, choose Pulumi. Otherwise, go with Terraform. Either way, there is no wrong choice with Terraform or Pulumi. Both are more than capable of meeting your IaC requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you are looking to manage infrastructure as code, &lt;a href="https://spacelift.io/" rel="noopener noreferrer"&gt;Spacelift&lt;/a&gt; is the way to go. It supports Git workflows, policy as code, programmatic configuration, context sharing, and many more great features. It currently works with &lt;a href="https://docs.spacelift.io/vendors/terraform/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;, Pulumi, CloudFormation, and Kubernetes and has initial &lt;a href="https://spacelift.io/blog/spacelift-ansible-integration-beta" rel="noopener noreferrer"&gt;support for Ansible&lt;/a&gt;. You can test drive it by creating a &lt;a href="https://spacelift.io/free-trial" rel="noopener noreferrer"&gt;free trial account&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>terraform</category>
      <category>infrastructureascode</category>
      <category>pulumi</category>
    </item>
    <item>
      <title>How to Use Terraform `depends_on`</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Thu, 27 Oct 2022 04:00:00 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/how-to-use-terraform-dependson-3a0k</link>
      <guid>https://dev.to/pdelcogliano/how-to-use-terraform-dependson-3a0k</guid>
      <description>&lt;p&gt;In this article you will learn why and how to use Terraform's &lt;code&gt;depends_on&lt;/code&gt; meta-argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource Dependencies
&lt;/h2&gt;

&lt;p&gt;Terraform uses resource dependencies to create and destroy resources in the correct order. &lt;a href="https://developer.hashicorp.com/terraform/language/expressions/references" rel="noopener noreferrer"&gt;Expression references&lt;/a&gt; create &lt;em&gt;implicit&lt;/em&gt; dependencies between one resource and another resource. The following shows an implicit dependency created between two Azure resources using an expression reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"rg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-example-centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"centralus"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_virtual_network"&lt;/span&gt; &lt;span class="s2"&gt;"vnet"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vnet-example-centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, an implicit dependency is created because the VNet's &lt;code&gt;resource_group_name&lt;/code&gt; attribute references the name of the resource group. The object with the dependency, the VNet, is referred to as the "object declaring the dependency", and the resource group is referred to as the "dependency object". Terraform completes all actions on the dependency object first, then performs actions on the object declaring the dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Terraform &lt;code&gt;depends_on&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In the majority of scenarios, Terraform automatically determines dependencies between resources based on expressions within a resource block. There are rare scenarios, however, where Terraform cannot infer dependencies between resources. In these cases, you will need to create an &lt;em&gt;explicit&lt;/em&gt; dependency. This is the purpose of the &lt;code&gt;depends_on&lt;/code&gt; meta-argument. &lt;code&gt;depends_on&lt;/code&gt; allows you to create an explicit dependency between two resources.&lt;/p&gt;

&lt;p&gt;Dependencies are not limited to just resources. Dependencies can be created between modules. When the dependency is a module, &lt;code&gt;depends_on&lt;/code&gt; affects the order in which Terraform processes all the resources and data sources associated with that module. Both implicit and explicit dependencies affect the order in which resources are destroyed as well as created. Explicit dependencies are only necessary when a resource or module relies on another resource's behavior but does not access any of that resource's data in its arguments.&lt;/p&gt;

&lt;p&gt;There are some things you should consider when using &lt;code&gt;depends_on&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Always include a comment to explain why using &lt;code&gt;depends_on&lt;/code&gt; is necessary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;depends_on&lt;/code&gt; causes Terraform to create a more conservative plan. The plan may modify more resources than necessary. For example, there may be more values as unknown “(known after apply)”. This is more likely to occur when creating explicit dependencies between modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adding explicit dependencies can increase the length of time it takes to build your infrastructure because Terraform must wait until the dependency object is created before continuing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It is recommended to use expression references to create implicit dependencies whenever possible.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Cases for &lt;code&gt;depends_on&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Some scenarios that may require the use of an explicit dependency include running a SQL script after a DB is created, deploying containers only after the container registry is created, or you have an application running on a VM instance that expects to use a specific Azure Storage Container. This dependency is not visible to Terraform since the application’s architecture controls it. The following snippet demonstrates an explicit dependency between a VM and a storage container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_windows_virtual_machine"&lt;/span&gt; &lt;span class="s2"&gt;"vm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-demo"&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vm-demo"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"centralus"&lt;/span&gt;

  &lt;span class="nx"&gt;size&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard_F2"&lt;/span&gt;
  &lt;span class="nx"&gt;admin_username&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"adminuser"&lt;/span&gt;
  &lt;span class="nx"&gt;admin_password&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"P@&lt;/span&gt;&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;w0rd1234!"&lt;/span&gt;
  &lt;span class="nx"&gt;network_interface_ids&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm_network_interface&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;os_disk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;caching&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ReadWrite"&lt;/span&gt;
    &lt;span class="nx"&gt;storage_account_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard_LRS"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# all other relevant attributes&lt;/span&gt;

 &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;st&lt;/span&gt;
 &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"st"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-demo"&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stdemo"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;account_tier&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard"&lt;/span&gt;
  &lt;span class="nx"&gt;account_replication_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LRS"&lt;/span&gt;
  &lt;span class="c1"&gt;# all other relevant attributes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding Multiple Dependency Objects
&lt;/h3&gt;

&lt;p&gt;Multiple dependencies can be passed to the &lt;code&gt;depends_on&lt;/code&gt; argument using a comma separated list of resources. Note: adding explicit dependencies can increase the length of time it takes to create your infrastructure because Terraform will wait to create the dependent resource until after the specified resource is created. The following snippet expands on the example from above and adds a second dependency, an Azure SQL Server, to the VM. This example is for illustration purposes and does not represent the best use case for creating an explicit dependency to multiple resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_windows_virtual_machine"&lt;/span&gt; &lt;span class="s2"&gt;"vm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# all other relevant attributes&lt;/span&gt;

 &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm_mssql_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;
 &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_mssql_server"&lt;/span&gt; &lt;span class="s2"&gt;"sql"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-demo"&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"stdemo"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;                      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;administrator_login&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"adminuser"&lt;/span&gt;
  &lt;span class="nx"&gt;administrator_login_password&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"P@&lt;/span&gt;&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;w0rd1234!"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"12.0"&lt;/span&gt;
  &lt;span class="c1"&gt;# all other relevant attributes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How to use &lt;code&gt;depends_on&lt;/code&gt; for modules in Terraform
&lt;/h3&gt;

&lt;p&gt;As mentioned above, resources can have explicit dependencies on modules, and vice-versa. This next example uses a module from the Terraform registry to create a VM. The module will be dependent upon the successful creation of the storage account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"virtual-machine"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Azure/network/azurerm"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-demo"&lt;/span&gt;
  &lt;span class="c1"&gt;# all other relevant attributes&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;azurerm_storage_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;st&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"st"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# all other relevant attributes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's an example of the same two resources with the dependency reversed. In this example the storage account resource is dependent upon the vm module. Note the use of the &lt;code&gt;module&lt;/code&gt; keyword in the dependency resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"virtual-machine"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Azure/network/azurerm"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-demo"&lt;/span&gt;
  &lt;span class="c1"&gt;# all other relevant attributes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"st"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# all other relevant attributes&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;virtual-machine&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understanding how Terraform uses implicit and explicit dependencies helps you to determine when and how to use the &lt;code&gt;depends_on&lt;/code&gt; meta-argument. When necessary, &lt;code&gt;depends_on&lt;/code&gt; instructs Terraform to create and destroy resources in the correct order. Misusing the argument can lead to potentially confusing, poor performing code.&lt;/p&gt;

</description>
      <category>iac</category>
      <category>terraform</category>
      <category>devops</category>
      <category>azure</category>
    </item>
    <item>
      <title>Check Your IaC Deployment with the Terraform `plan` Command</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Wed, 28 Sep 2022 15:53:42 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/check-your-iac-deployment-with-the-terraform-plan-command-41lj</link>
      <guid>https://dev.to/pdelcogliano/check-your-iac-deployment-with-the-terraform-plan-command-41lj</guid>
      <description>&lt;p&gt;Infrastructure as Code (IaC) is a key attribute of enabling best practices in any organization practicing a DevOps culture. The ability to build up and tear down infrastructure via code has many benefits, like reducing cost, increasing deployment speed, and lowered risk through consistency. There is, however, a dark side to IaC. If not used cautiously and carefully, it is way too easy to cause harm to an environment.&lt;/p&gt;

&lt;p&gt;Applying infrastructure changes haphazardly can lead to unwanted changes to your environment. The Terraform &lt;a href="https://www.terraform.io/cli/commands/plan" rel="noopener noreferrer"&gt;&lt;code&gt;plan&lt;/code&gt;&lt;/a&gt; command is available to bring visibility to your IaC deployments. The &lt;code&gt;plan&lt;/code&gt; command reports on changes to infrastructure but it does not apply any of the proposed changes. Instead, it creates a reviewable execution plan, which you can use to confirm that the proposed changes are expected. The execution plan can be saved to a file, which can be applied later, or peer reviewed to enforce quality control. The ability to review the proposed changes prior to applying them can save you from making unexpected infrastructure changes. After you have confirmed the changes as expected, use the Terraform &lt;code&gt;apply&lt;/code&gt; command to update the remote objects.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;plan&lt;/code&gt; command does three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ensures the state is up to date by reading the current state of any already-existing remote infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Determines the deltas between the current configuration and the prior state data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Proposes a series of changes that will make the remote infrastructure match the current configuration.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there aren't any deltas, the output will report that no actions need to be taken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;p&gt;Let's look at the output resulting from executing &lt;code&gt;plan&lt;/code&gt; with a simple terraform configuration script. For this walkthrough, we'll use a new Azure subscription that does not contain any resources. The listing for the configuration script is below.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;

&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"=3.0.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"tf-plan"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-tf-plan-example-centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"centralus"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Run the following command on the command line to generate an execution plan. Before doing so, be sure to set environment variables for your Azure Subscription Id and Tenant Id, using the subscription id and tenant id from your Azure subscription, respectively. For more details on the different ways you can configure the AzureRM Terraform provider, see &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvs3871f9niztofn53gf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvs3871f9niztofn53gf.png" alt="Output from plan command"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output contains a wealth of information, which is highlighted in the image above. Terraform proposed a &lt;em&gt;create&lt;/em&gt; action, as indicated by the green plus sign. Changes and deletions appear as a yellow tilda and red minus sign, respectively. Terraform is also proposing to create a new resource group named "rg-tf-plan-example-centralus". The execution plan includes a summary count of each action in the proposal. In this case there is one &lt;em&gt;add&lt;/em&gt;, zero &lt;em&gt;change&lt;/em&gt;, and zero &lt;em&gt;destroy&lt;/em&gt;, actions.&lt;/p&gt;

&lt;p&gt;Apply the proposed changes by running the &lt;code&gt;apply&lt;/code&gt; command &lt;em&gt;(make sure you confirm the actions by typing &lt;code&gt;yes&lt;/code&gt; when prompted)&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In addition to creating the resource group, terraform also updated the local &lt;a href="https://www.terraform.io/cli/state" rel="noopener noreferrer"&gt;state&lt;/a&gt; data. Terraform uses state data to remember the remote infrastructure configurations and create deltas when configurations change.&lt;/p&gt;

&lt;p&gt;Make the following change to the sample terraform script. Add the following line to the end of the script, just before the closing curly brace:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tags = {name: "test"}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The resource group resource should now look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"tf-plan"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-tf-plan-example-centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Re-run the &lt;code&gt;plan&lt;/code&gt; command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When the &lt;code&gt;plan&lt;/code&gt; command executed, Terraform compared the stored state data for the &lt;em&gt;rg-tf-plan-example-centralus&lt;/em&gt; resource to the changes made in the configuration script.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1eifdb8kjk31katim70.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1eifdb8kjk31katim70.png" alt="terraform plan delta output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at the output, you can see Terraform determined an update was necessary. The output also shows the proposed changes, including the actual change itself: the addition of the &lt;code&gt;name&lt;/code&gt; tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning Modes
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;plan&lt;/code&gt; command can be run in three different modes, &lt;em&gt;normal&lt;/em&gt;, which is the default and the mode we've used so far, &lt;em&gt;destroy&lt;/em&gt;, and &lt;em&gt;refresh-only&lt;/em&gt;. The modes are mutually exclusive. You cannot use more than one mode at a time.&lt;/p&gt;

&lt;p&gt;Destroy mode creates a plan which shows you all the remote objects that will be removed. Running the command in &lt;em&gt;destroy&lt;/em&gt; mode does not actually perform any actions. A great use case for &lt;em&gt;destroy&lt;/em&gt; mode is to delete a temporary development environment. You activate &lt;em&gt;destroy&lt;/em&gt; mode by including the &lt;code&gt;-destroy&lt;/code&gt; option on the command line:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan -destroy&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1hnkixowp6e67k6nzy3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1hnkixowp6e67k6nzy3h.png" alt="terraform destroy output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are times when you will intentionally change remote objects without the use of a Terraform script, i.e., directly via the Azure CLI. This can be necessary when troubleshooting a problem in the remote environment. In these cases, the state data will not match the remote object configuration and will need to be reconciled. Executing &lt;code&gt;plan&lt;/code&gt; in &lt;em&gt;refresh-only&lt;/em&gt; mode creates a plan with the goal of showing you the deltas between the state data and the remote objects. You can activate refresh-only mode by including the &lt;code&gt;-refresh-only&lt;/code&gt; option on the command line. Note: the -refresh-only option is available only in Terraform v0.15.4 and later.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan -refresh-only&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqjj7vhlokoq0wv9z9a5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjqjj7vhlokoq0wv9z9a5.png" alt="terraform refresh output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning Options
&lt;/h2&gt;

&lt;p&gt;In addition to the alternate &lt;code&gt;plan&lt;/code&gt; modes described above, there are a couple of other categories of options available, those affecting behavior and those affecting outputs. Options that modify the &lt;code&gt;plan&lt;/code&gt; command's behavior are listed below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Options Affecting How the Plan Command Behaves
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-refresh=false&lt;/td&gt;
&lt;td&gt;Ignores external changes to remote objects, i.e., those made via the CLI. This option will not sync the state data with the remote objects before checking for configuration changes. While this improves performance by making less remote API requests, ignoring external changes could result in incomplete or incorrect plans.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-replace=ADDRESS&lt;/td&gt;
&lt;td&gt;Forces the replacement of the resource instead of an &lt;em&gt;update&lt;/em&gt; action or no change at all. This will replace the resource at the given address. It can be applied multiple times to replace many objects at once.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-var 'NAME=VALUE'&lt;/td&gt;
&lt;td&gt;Applies a value for an input variable. It can be applied multiple times, once per variable. Note: Errors will occur if a space appears before or after the equals sign (e.g., -var "region = centralus").&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-var-file=FILENAME&lt;/td&gt;
&lt;td&gt;Applies values for one or more input variables as defined in a "tfvars" file. It can be used multiple times, once for each "tfvars" file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-lock=false&lt;/td&gt;
&lt;td&gt;State data is unlocked during the operation. Note: This can lead to concurrency issues especially if others are running commands against the same workspace.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-lock-timeout=DURATION&lt;/td&gt;
&lt;td&gt;Instructs Terraform to retry acquiring a lock for a period of time before returning an error. The duration value must be entered using the format &lt;em&gt;nt&lt;/em&gt; where n is a number and t is a letter representing a unit of time, e.g., &lt;em&gt;3m&lt;/em&gt; is 3 minutes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's turn the the resource group's &lt;em&gt;location&lt;/em&gt; attribute into a variable. This adds flexibility and reusability to the script, allowing us to specify the location value at runtime. Modify the sample script as follows.&lt;/p&gt;

&lt;p&gt;Add a variable to the sample script, named "region", just before the &lt;em&gt;provider&lt;/em&gt; section and modify the &lt;em&gt;azurerm_resource_group&lt;/em&gt; resource so the value for location attribute comes from a variable. Note: for simplicity, we are using one ".tf" file to define the resources and the variables. In practice, variables are declared in a separate file, typically named "variables.tf". The script below shows the result of making this change.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Azure location for the resource group"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"azurerm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"tf-plan"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-tf-plan-example-centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt; 
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The "region" variable is declared as a string. Re-run the Terraform &lt;code&gt;plan&lt;/code&gt; command. This time you will be prompted for the location in Azure where the resource group resides.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpfze8y4yslvwjwbe9keh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpfze8y4yslvwjwbe9keh.png" alt="prompt for variables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run the &lt;code&gt;plan&lt;/code&gt; command again. This time, using the &lt;em&gt;-var&lt;/em&gt; option to provide a value for region on the command line, and notice that you are no longer prompted to supply a value for the variable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan -var='region="centralus"'&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Options Related to Formatting and Output
&lt;/h3&gt;

&lt;p&gt;There will be use-cases where you will need to change the &lt;code&gt;plan&lt;/code&gt; command's output. The following options are available for those use-cases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;-compact-warnings&lt;/td&gt;
&lt;td&gt;Shows warnings as a summary unless the warnings include errors. If errors, the text will include useful information for troubleshooting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-input=false&lt;/td&gt;
&lt;td&gt;Disables prompts for input variables. that have not otherwise been assigned a value. This option is useful when automating Terraform scripts, like when run in a CI/CD pipeline.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-json&lt;/td&gt;
&lt;td&gt;Returns output in JSON format.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-no-color&lt;/td&gt;
&lt;td&gt;Removes color from the output.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-out=FILENAME&lt;/td&gt;
&lt;td&gt;Saves the plan to a file that can be passed to the Terraform &lt;code&gt;apply&lt;/code&gt; command to execute the planned changes. Any filename is allowed, but the recommended naming convention is to name it "tfplan". Do not use the ".tf" suffix as this will lead Terraform to interpret the file as a configuration script, and will fail. Note: the file contains all the values associated with planned changes, including any sensitive data, in cleartext in the plan file. For this reason, you must consider impacts to security when saving plans to a file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-parallelism=n&lt;/td&gt;
&lt;td&gt;By default, Terraform will run 10 operations concurrently, where possible. This option limits the number of concurrent operations to &lt;em&gt;n&lt;/em&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-detailed-exitcode&lt;/td&gt;
&lt;td&gt;Includes detailed exit codes which provide more granular information about the resulting plan. The list of codes is:&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;0 = Succeeded with empty diff (no changes)&lt;/li&gt;
&lt;li&gt;1 = Error&lt;/li&gt;
&lt;li&gt;2 = Succeeded with non-empty diff (changes present)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resource Targeting
&lt;/h2&gt;

&lt;p&gt;Resource targeting is like the &lt;em&gt;-replace=ADDRESS&lt;/em&gt; option in that it tells Terraform to focus the plan only on resources that match the given address. Resource targeting includes all other objects that the selected resource(s) depend on, either directly or indirectly.&lt;/p&gt;

&lt;p&gt;You can target resources using the &lt;em&gt;-target=ADDRESS&lt;/em&gt; option. Note: It is not recommended to use &lt;em&gt;-target&lt;/em&gt; under normal circumstances. Terraform enforces this point by including a warning message in the output. Run the following command to see the warning message embedded in the output:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan -var='region="centralus"' -target='azurerm_resource_group.tf-plan'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguj4syux1nfyn7gv848k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguj4syux1nfyn7gv848k.png" alt="terraform warning from target option"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of using &lt;em&gt;-target&lt;/em&gt;, split large configurations into several smaller configurations that can each be applied independently. Doing so allows a complicated configuration to be separated into more manageable parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;plan&lt;/code&gt; command is very powerful despite not actually modifying any resources. Saving plans to output files enables automation and peer review. Supplying variable values on the command line, or in files, eliminates the need to hard code values, some of which may be sensitive. Using &lt;code&gt;plan&lt;/code&gt; to show deltas between state data and remote configurations can help illuminate changes made directly to remote objects, which may need to be refreshed.&lt;/p&gt;

&lt;p&gt;You should &lt;em&gt;plan&lt;/em&gt; to include the &lt;code&gt;plan&lt;/code&gt; command prior to applying your changes. This will help you avoid late night troubleshooting sessions for changes that have gone awry. Remember, failing to plan is planning to fail.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>iac</category>
    </item>
    <item>
      <title>Practice DRY in Terraform Configurations Using a Dynamic Block</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Fri, 15 Jul 2022 22:59:19 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/practice-dry-in-terraform-configurations-using-a-dynamic-block-52nm</link>
      <guid>https://dev.to/pdelcogliano/practice-dry-in-terraform-configurations-using-a-dynamic-block-52nm</guid>
      <description>&lt;p&gt;The &lt;em&gt;Don't Repeat Yourself&lt;/em&gt; (DRY) principle of software development states that code should be written once and not repeated. An important goal of the DRY principle is to improve the maintainability of code.&lt;/p&gt;

&lt;p&gt;See how you can &lt;a href="https://spacelift.io/blog/terragrunt-with-spacelift" rel="noopener noreferrer"&gt;keep your configuration DRY with Terragrunt on Spacelift&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Some Terraform resources include repeatable nested blocks in their arguments. These nested blocks represent separate resources that are related to the containing resource. For example, Azure VNets contain a subnet block attribute which repeats for each subnet within the VNet. This can lead to repeated configuration blocks in a Terraform script, which violates the DRY principle.&lt;/p&gt;

&lt;p&gt;Dynamic blocks are a solution for applying DRY in terraform configuration scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use the Dynamic Blocks
&lt;/h2&gt;

&lt;p&gt;Terraform provides the &lt;code&gt;dynamic&lt;/code&gt; block to create repeatable nested blocks within a resource. A &lt;code&gt;dynamic&lt;/code&gt; block is similar to the &lt;code&gt;for&lt;/code&gt; expression. Where &lt;code&gt;for&lt;/code&gt; creates repeatable top-level resources, like VNets, &lt;code&gt;dynamic&lt;/code&gt; creates nested blocks &lt;em&gt;within&lt;/em&gt; a top-level resource, like subnets within a VNet. A &lt;code&gt;dynamic&lt;/code&gt; block iterates over a child resource and generates a nested block for each element of that resource.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;The following code shows the configuration of an Azure VNet and four subnets. In this example, the subnet blocks are written out explicitly, creating repeated code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_virtual_network"&lt;/span&gt; &lt;span class="s2"&gt;"dynamic_block"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vnet-dynamicblock-example-centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dynamic_block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dynamic_block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;address_space&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.10.0.0/16"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;subnet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"snet1"&lt;/span&gt;
    &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.1.0/24"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;subnet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"snet2"&lt;/span&gt;
    &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.2.0/24"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;subnet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"snet3"&lt;/span&gt;
    &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.3.0/24"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;subnet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"snet4"&lt;/span&gt;
    &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.4.0/24"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That same configuration using a &lt;code&gt;dynamic&lt;/code&gt; block is shown below. Replacing the four &lt;code&gt;subnet&lt;/code&gt; blocks with a &lt;code&gt;dynamic&lt;/code&gt; block removes repeated attributes, leading to cleaner code that is easier to maintain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_virtual_network"&lt;/span&gt; &lt;span class="s2"&gt;"dynamic_block"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vnet-dynamicblock-example-centralus"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dynamic_block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dynamic_block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;address_space&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.10.0.0/16"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"subnet"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnets&lt;/span&gt;
    &lt;span class="nx"&gt;iterator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;   &lt;span class="c1"&gt;#optional&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
      &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address_prefix&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the definition of the variable &lt;em&gt;subnets&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"subnets"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"list of values to assign to subnets"&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
    &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The values for the &lt;em&gt;subnets&lt;/em&gt; variable are defined in a tfvars file. Sample values are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;subnets&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"snet1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.1.0/24"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"snet2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.2.0/24"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"snet3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.3.0/24"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"snet4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address_prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.10.4.0/24"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dynamic Block Components
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dynamic&lt;/code&gt; blocks are supported inside of &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;provider&lt;/code&gt;, and &lt;code&gt;provisioner&lt;/code&gt; blocks. A &lt;code&gt;dynamic&lt;/code&gt; block consists of the following components:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;label&lt;/td&gt;
&lt;td&gt;Specifies what kind of nested block to generate. In the above example, the label is "subnet". A subnet resource will be generated for each element in the &lt;code&gt;var.subnets&lt;/code&gt; variable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;for_each&lt;/td&gt;
&lt;td&gt;The complex value to iterate over.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iterator&lt;/td&gt;
&lt;td&gt;(optional) Sets the name of a temporary variable that represents the current element. If not provided, the name of the variable defaults to the label of the dynamic block. An iterator has two attributes: &lt;em&gt;key&lt;/em&gt; and &lt;em&gt;value&lt;/em&gt;. Key is the element index. Value is the element value.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;content&lt;/td&gt;
&lt;td&gt;Defines the body of each generated block.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A &lt;code&gt;dynamic&lt;/code&gt; block can only generate attributes that belong to the resource type being created. It isn't possible to generate meta-argument blocks like &lt;code&gt;lifecycle&lt;/code&gt;. Some resource types have blocks with multiple levels of nesting. When working with multi-level nested blocks, the iterators for each block are important. It is recommended to use the optional &lt;code&gt;iterator&lt;/code&gt; component to clearly define each level in the configuration, and to remove any ambiguities resulting from attributes with the same name as a parent block. The listing below illustrates a nested block example, with clearly defined iterators for each block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;  &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"origin_group"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;load_balancer_origin_groups&lt;/span&gt;
    &lt;span class="nx"&gt;iterator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outer_block&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outer_block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;

      &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"origin"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;outer_block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origins&lt;/span&gt;
        &lt;span class="nx"&gt;iterator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inner_block&lt;/span&gt;
        &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inner_block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Points
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;dynamic&lt;/code&gt; block is a great way to apply the DRY principle in Terraform configuration scripts. Implementing a &lt;code&gt;dynamic&lt;/code&gt; block where appropriate removes repeated code leading to configurations which are easier to read, maintain, and reuse - just as the DRY principle aims to do.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>iac</category>
    </item>
    <item>
      <title>An Introduction to DevOps with Azure DevOps</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Mon, 09 May 2022 12:22:38 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/an-introduction-to-devops-with-azure-devops-332i</link>
      <guid>https://dev.to/pdelcogliano/an-introduction-to-devops-with-azure-devops-332i</guid>
      <description>&lt;p&gt;Want to transform your company into an Agile powerhouse? To do that you'll need to start with a solid foundation built on the ideas and concepts of a DevOps culture. Once you have that foundation established you can look to technical solutions to convert your company's culture and technology. &lt;/p&gt;

&lt;p&gt;This article introduces DevOps concepts and terminology, then introduces each of the features and services available from Microsoft's Azure DevOps suite of services and connects each service to the concepts of a DevOps culture.&lt;/p&gt;

&lt;p&gt;Let's begin by defining "DevOps". DevOps is a culture whose goal is to change the way developers and IT staff collaborate to deliver software. The DevOps culture allows a software development team to better respond to business requirements, increase confidence in the software it delivers, and rapidly deploy high-quality software.&lt;/p&gt;

&lt;p&gt;DevOps influences Application Lifecycle Management (ALM) through its planning, developing, delivering, and operating phases. Each phase is dependent upon its prior phase. Phases are not role specific. Collaboration is key in a true DevOps culture. To that end, each role is involved in all phases to some degree. The four phases are described below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Planning
&lt;/h2&gt;

&lt;p&gt;A software project starts at the planning phase. During this phase teams generate ideas, define the project, and describe the application and capabilities. Backlogs and sprints are used to manage the project. Progress can be tracked at differing levels of granularity, from single-product tasks to tasks that span many products. Scrum and Kanban boards are used to visualize progress using dashboards and reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developing
&lt;/h2&gt;

&lt;p&gt;During the development phase all aspects of application development take place, including code writing, testing, reviewing, and the management of source code. Development teams practicing a DevOps culture use Continuous Integration (CI) to achieve the goals of the developing phase. Continuous Integration is the practice of automating code merging and testing. CI helps a team to identify issues early in the development cycle, making them less expensive to resolve. The CI process typically includes automated tests which help to ensure code quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delivery
&lt;/h2&gt;

&lt;p&gt;The delivery phase includes the deployment of applications into various environments in a consistent and reliable way. Releases are controlled with manual approvals. Automation moves applications between environments all the way up to production. Teams implement Continuous Delivery (or Continuous Deployment) (CD) to meet the goals of the delivery phase.&lt;/p&gt;

&lt;p&gt;Continuous Delivery is a process by which code is built, tested, and deployed to one or environments. Deploying and testing in multiple environments increases quality. The entire delivery process is scalable, repeatable, and controllable using automation. CD produces outputs which are deployable. Assets like executables, configuration files and even infrastructure can be included in the CD output. Teams use tools like Terraform, Bicep, or ARM templates to deploy infrastructure in a repeatable, reliable manner. These tools enable DevOps teams to create and tear down infrastructure via code, aka Infrastructure as Code (IaC).  Monitoring and notification systems are constantly running to provide visibility into the whole CD process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operating
&lt;/h2&gt;

&lt;p&gt;Teams adopting DevOps practices aim to ensure their systems are reliable, available, secure, and compliant. The operating phase involves maintaining, monitoring, and troubleshooting applications in production environments. DevOps teams look to identify issues in production before they affect the end-user's experience. They want to mitigate issues as soon as they occur. To meet these goals, teams require notifications and robust application logging.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Azure DevOps?
&lt;/h2&gt;

&lt;p&gt;Once a company buys into the DevOps culture it needs a toolset to implement the Agile principles of delivering high-quality software. This is where Microsoft's Azure DevOps suite of services fits in. Azure DevOps provides tools for every phase of ALM including planning, development, delivery, and operations. You can choose how many of the Azure DevOps services to implement, from one to all. The services available within Azure DevOps are shown in the figure below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648342370%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsToolset_dd9s8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648342370%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsToolset_dd9s8s.png" title="Figure 1: Azure DevOps service categories" alt="Azure DevOps service categories"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Azure DevOps is available in two flavors, Azure DevOps Server and Azure DevOps Services. Azure DevOps Server is a self-hosted, on-premises installation of Azure DevOps. This version shares the same features as the cloud offering, Azure DevOps Services but makes you responsible for security, configuration, and all the other responsibilities of maintaining software in your data center. Organizations may choose Azure DevOps Server because compliance or security requirements necessitate the need to keep data within its data center. Integrating with other on-premises services like SQL Server Analysis Server would be another use case for choosing the on-prem version. Those use cases are exceptional. Most development shops will choose Azure DevOps Services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure DevOps Services
&lt;/h2&gt;

&lt;p&gt;Azure DevOps Services is a cloud-based, Software-as-a-Service (SaaS) offering. The SaaS version is part of the Microsoft Azure cloud platform. It reaps all the benefits of a SaaS offering, including 24x7 availability, automatic updates, and Azure AD security. Azure DevOps Services requires no extra configuration. Getting started is quick and easy, using your Microsoft account to sign up and create an organization (domain), a project, and add a new user.&lt;/p&gt;

&lt;p&gt;The Azure DevOps Web portal provides access to all features You can use all the services included with Azure DevOps or choose just what you need to compliment your existing workflows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Organization
&lt;/h3&gt;

&lt;p&gt;Azure DevOps is arranged in a hierarchical structure. At the top of the hierarchy sits the organization. An Azure DevOps organization is a container of one or more related projects. Each Azure DevOps account can contain one or more organizations. A good rule of thumb is to start with one organization and add others as needed based on factors like security models, business units within the company, and the company's development teams. Organizations are isolated from one another. It is easier to partition an organization using projects than it is to combine different organizations. A good rule of thumb is to stick with one organization when possible.&lt;/p&gt;

&lt;p&gt;Each organization has its own URL in the form of &lt;code&gt;https://dev.azure.com/&amp;lt;Organization Name&amp;gt;&lt;/code&gt; and gets its own set of free tier resources (more to come on these resources in a bit) including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jobs for executing a CI/CD pipeline&lt;/li&gt;
&lt;li&gt;Tools for tracking work items, defects using Kanban or Scrum&lt;/li&gt;
&lt;li&gt;An unlimited amount of private Git repos&lt;/li&gt;
&lt;li&gt;5 free users (basic)&lt;/li&gt;
&lt;li&gt;2 GiB Artifact storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Organizations can be created using an Azure Active Directory (Azure AD) or a Microsoft account. The account used influences the organization's security model. Use an MS account when you don't need to authenticate with an Azure AD. For stricter control, create an organization using an Azure AD account. This restricts access to the organization to only those AD users who are members in that directory. Removing a user from AD removes his/her access from the organization. This provides richer security because the Azure AD is managed by the administrator and can be controlled company wide. Users can be administered using Azure AD groups, further facilitating access control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Projects
&lt;/h3&gt;

&lt;p&gt;The next level down in the hierarchy is the Project. An Azure DevOps project is a grouping of services and features that are available through the Web portal or an IDE client, like Visual Studio. When you create your organization, a default project is created for you.&lt;/p&gt;

&lt;p&gt;The screen shot below shows the options available during when creating a new project. The choices made during creation will influence the behavior and functionality of the tools within the project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648394956%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsProjectSetup_qxevy5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648394956%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsProjectSetup_qxevy5.png" title="Azure DevOps Project Setup" alt="Azure DevOps Project setup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A project can be made available to the public or a private group. The &lt;em&gt;Version control&lt;/em&gt; choice under the &lt;em&gt;Advanced&lt;/em&gt; drop down determines the type of source control repository. There are two options, &lt;em&gt;Git&lt;/em&gt; and &lt;em&gt;Team Foundation Version Control&lt;/em&gt;. Most teams will choose Git. The &lt;em&gt;Work item process&lt;/em&gt; drop down allows you to choose the project's process model. This &lt;a href="https://docs.microsoft.com/en-us/azure/devops/boards/work-items/guidance/choose-process?view=azure-devops&amp;amp;tabs=basic-process" rel="noopener noreferrer"&gt;link provides detailed information about process models.&lt;/a&gt; The table below summarizes each option available.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Process Model&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Provides the simplest model that tracks work through Issues, Tasks, and Epics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agile&lt;/td&gt;
&lt;td&gt;Supports Agile planning methods including Scrum, and tracks development and test activities separately. This process works great if you want to track user stories and (optionally) bugs on the Kanban board, or track bugs and tasks on the taskboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scrum&lt;/td&gt;
&lt;td&gt;Tracks work using product backlog items (PBIs) and bugs on the Kanban board or viewed on a sprint taskboard. This process supports the Scrum methodology as defined by the Scrum organization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capability Maturity Model Integration (CMMI)&lt;/td&gt;
&lt;td&gt;Supports a framework for process improvement and an auditable record of decisions. With this process, you can track requirements, change requests, risks, and reviews. This process supports formal change management activities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An organization can have one or more projects. Much like the decisions to create multiple organizations, several factors should be considered when determining to create multiple projects. Unique security and access control requirements, staggered release schedules, disparate code bases, and diverse development teams are some reasons why you would create multiple projects. Single projects are great for aligning multiple teams along the same cadence, with common goals and release schedules. Be cautious using a single project for your entire enterprise. Poorly organized folder structures or bad naming conventions can make searching across a single project difficult. A best practice here is to create one project per source code repository.&lt;/p&gt;

&lt;p&gt;Beneath the project hierarchy lies five child services, &lt;code&gt;Boards&lt;/code&gt;, &lt;code&gt;Repos&lt;/code&gt;, &lt;code&gt;Pipelines&lt;/code&gt;, &lt;code&gt;Test Plans&lt;/code&gt;, and &lt;code&gt;Artifacts&lt;/code&gt;. An individual project has its own set of services which are isolated from the other projects. The list below describes each of the standalone services available within a project. These may differ depending upon the options you selected when creating the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Boards: Tools for planning and tracking work items, code defects and issues using Kanban and Scrum methods are available from the Azure Boards service.&lt;/li&gt;
&lt;li&gt;Repos: source control is provided by the Azure Repos service.&lt;/li&gt;
&lt;li&gt;Pipelines: Build pipeline for Continuous Integration and Continuous Deployment (CI/CD) functionality.&lt;/li&gt;
&lt;li&gt;Test Plans: Tools for testing applications, both manual and automated.&lt;/li&gt;
&lt;li&gt;Artifacts: The sharing of integrated packages like npm and NuGet is available from the Azure Artifacts service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Boards
&lt;/h3&gt;

&lt;p&gt;The set of tools available under the Azure DevOps &lt;code&gt;Boards&lt;/code&gt; category are useful for managing a project during the ALM planning phase. The tools provide a rich set of capabilities including support for Agile, Scrum, and Kanban processes. Tools like workflow items, backlogs, sprints, and configurable dashboards are used for tracking and reporting a project's progress. These tools are designed to scale as the business grows and help the team to plan and manage a project throughout its lifecycle.&lt;/p&gt;

&lt;p&gt;When starting a new project, teams will begin with setting up the "board". Everything begins with a work-item. The &lt;em&gt;Work Items&lt;/em&gt; link is used for creating items that represent a task. Work-items contain details about the task, such as its type - it can be a bug, task, epic, or a user story, its priority, risk level, assignee, any related child or parent tasks, and various dates. The following screenshot shows a task I created for writing this post.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648399529%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsNewworkItem_lhgfjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648399529%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsNewworkItem_lhgfjf.png" title="New Work Item" alt="New Work Item"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The DevOps team sets up all the work-items for a project. Views show the work items in various stages of development. The stage names are customizable by the team. The default stages are "New", "Active", "Resolved" and "Closed". The following screen shot shows the &lt;em&gt;boards&lt;/em&gt; link in my sample project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648399795%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsBoard_wpmvkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648399795%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsBoard_wpmvkg.png" title="DevOps Board" alt="DevOps Board"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Backlogs&lt;/code&gt; link provides a visual method for assigning work-items to a sprint. Using the backlog, work-items can be dragged to the Planning cards and dropped into an Iteration. An iteration is the default name for sprints in Azure DevOps. The iteration name is customizable. In the screen shot below, I moved my "Create Post Outline" task to the first sprint in the project.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648400511%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsBacklog_hh8aji.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648400511%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsBacklog_hh8aji.png" title="Backlog" alt="Backlog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additional planning features are provided by the &lt;em&gt;Sprints&lt;/em&gt;, &lt;em&gt;Queries&lt;/em&gt;, and &lt;em&gt;Delivery Plans&lt;/em&gt; links. Each tools provides functionality for managing the project. For example, items moved to a planning iteration are visible under the &lt;em&gt;Sprints&lt;/em&gt; link. This view lets teams track the status of the work-items in a specific sprint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repos
&lt;/h3&gt;

&lt;p&gt;Version control software like Git and Subversion enable teams to track changes made to code over time. As code is modified during the development phase, the version control system retains history of the changes. The version control system can also coordinate code changes across your team. A snapshot can be recalled later for comparison, restoration, and/or deployment. Regardless of project or team size, it is a good practice to use a version control solution.&lt;/p&gt;

&lt;p&gt;Azure DevOps offers the &lt;code&gt;Repos&lt;/code&gt; suite of version control tools. These tools allow DevOps teams to collaborate on development using free public and private repositories. As mentioned above, Azure Repos provides two version control systems to choose from: Git and TFVC. Primarily teams decide to use Git as it is a decentralized source control system and will receive most of the attention from Microsoft in the future. It also enables the greatest amount of flexibility and integrates with almost all tools in the developer ecosystem. A project can contain an unlimited amount of Git repos.&lt;/p&gt;

&lt;p&gt;Azure DevOps Repos offers a tremendous amount of functionality. Highlighted below are some of the more relevant features.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code can be shared using command-line, VS Code, or Visual Studio, among other IDEs.&lt;/li&gt;
&lt;li&gt;Code reviews are performed with pull requests to ensure that changes build without errors and pass tests before being merged into the main branch.&lt;/li&gt;
&lt;li&gt;Work-items can be linked to pull requests for project tracking.&lt;/li&gt;
&lt;li&gt;Policies and permissions can be applied to individual branches.&lt;/li&gt;
&lt;li&gt;Azure Functions can be used to create custom branch policies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In projects where the Azure Repos service is enabled, clicking on the &lt;em&gt;Repos&lt;/em&gt; link shows the following configuration options for initializing a repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648503712%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsNewRepo_jlwy3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648503712%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsNewRepo_jlwy3q.png" title="New repo" alt="New Repo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These options initialize a project's repository. An empty repository can be created, or the repository can be initialized with existing code via upload. Options are also available for creating a readme file and a &lt;em&gt;.gitignore&lt;/em&gt; file, which is a file that ensures certain files are not tracked by Git. Each development language has its own files to ignore. The setup options allow you to create a language specific &lt;em&gt;.gitignore&lt;/em&gt; file.&lt;/p&gt;

&lt;p&gt;DevOps teams with prior experience using Git will be familiar with the version control concepts Azure Repos implements. To get the most benefit from Repos, it is important to have a basic understanding of git version control concepts. The following table describes some of the relevant ones. You can read more about version control concepts &lt;a href="https://docs.microsoft.com/en-us/azure/devops/repos/get-started/key-concepts-repos?view=azure-devops" rel="noopener noreferrer"&gt;here.&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clone&lt;/td&gt;
&lt;td&gt;Cloning from your local PC creates a complete working copy of a repository on your local workstation. Changes made to the local repository can be &lt;em&gt;pushed&lt;/em&gt; to the remote repository in Azure DevOps.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commit&lt;/td&gt;
&lt;td&gt;A commit is a set of related code changes made to your local repository. These changes can be pushed or rolled back as a set.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branch&lt;/td&gt;
&lt;td&gt;Branches keep a history of commits and isolate code changes from the main branch. Branches are isolated, so committing changes to a branch doesn't affect other branches.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pull Request&lt;/td&gt;
&lt;td&gt;Pull requests allow the team to review code and provide feedback on changes before they are merged into a branch.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fork&lt;/td&gt;
&lt;td&gt;A fork is a repository copy of a repository. The copy includes all files, commits, and (optionally) branches from the remote. Files and branches in a forked repo are not shared between repositories. A pull request is required to merge forked files back into the main branch.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An Azure DevOps repository has a default branch named "main" or "master". The default branch should be protected from code that doesn't build properly. As a best practice, set up a branch policy that requires all merges into the default branch to be performed with a pull request (PR). This policy provides two benefits. First, it prevents changes to the main branch directly. This helps to ensure code quality by requiring a code review before changes are merged into the default branch. Second, the PR forces a clean build of the merged code before it is merged. Branching and release strategies are important concepts to understand. To learn more see &lt;a href="https://github.com/MicrosoftDocs/azure-devops-docs/blob/main/docs/repos/git/git-branching-guidance.md" rel="noopener noreferrer"&gt;"Adopt a Git branching strategy"&lt;/a&gt;. Branches and PRs can be managed via the &lt;em&gt;Branches&lt;/em&gt; and &lt;em&gt;Pull requests&lt;/em&gt; links, respectively.&lt;/p&gt;

&lt;p&gt;As mentioned above, a project can have multiple Git repositories. Multiple repositories are useful for projects that have independent deployment schedules. Multiple repositories will require separate security permissions, separate release schedules, and make sharing code across those repositories difficult. In most cases a single repository is the best approach. The overhead of maintaining multiple repositories isn't worth the effort vs. a single repository.&lt;/p&gt;

&lt;p&gt;Forks are useful in specific scenarios such as when working with developer teams that should not have direct access to update the main repository. Forks can also be useful when used in an open-source project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline
&lt;/h3&gt;

&lt;p&gt;As discussed in the delivery phase, CI/CD plays an important role in a DevOps organization. The &lt;code&gt;Pipelines&lt;/code&gt; service helps teams to set up and manage CI/CD for their projects. The ability to integrate code being worked on among many developers, and deploy releases through automation is a cornerstone of ensuring a quality product. Pipelines is flexible in that it works with many languages and project types. The pipeline itself is code, comprised of a series of stages, jobs, and steps, typically written using &lt;a href="https://yaml.org/" rel="noopener noreferrer"&gt;YAML&lt;/a&gt;. YAML is a human-readable, easy to understand language for creating configuration files. Azure pipelines integrates with Azure deployments, can build across platforms (Windows, Linux, or Mac), and can deploy to multiple environments. Azure pipelines is free for public projects. For private projects Azure provides 30 hours of pipeline jobs for free per month.&lt;/p&gt;

&lt;p&gt;Creating a new pipeline is done via the &lt;em&gt;pipelines&lt;/em&gt; link. This starts a wizard. The first step in the wizard is to provide the location of the source code repository. Choices are Azure Git Repos, GitHub, Bitbucket, Subversion, among others. Next, a repository from that location is chosen. The third step is to initialize the pipeline, using either a "starter pipeline" or an existing pipeline. Reviewing and saving the pipeline are the final steps in the wizard. The result is a file named "azure-pipelines.yml" which is automatically added to the project's repository. The following screenshot shows the azure-pipelines.yml file created by the starter pipeline wizard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648426456%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsNewPipeline4_w4b4me.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648426456%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsNewPipeline4_w4b4me.png" title="starter pipeline review" alt="starter pipeline review"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The starter pipeline mimics a typical "hello world" application. Running the pipeline outputs the string "hello world" as shown below. &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648426457%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsNewPipelineOutput_iuxqoy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648426457%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsNewPipelineOutput_iuxqoy.png" title="starter pipeline output" alt="starter pipeline output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two methods for creating pipelines, hand coding the azure-pipelines.yml file or choosing from a list of pre-defined tasks and configuring those tasks using a wizard. Azure provides a rich set of tasks to choose from. To learn more about the capabilities of a pipeline, spend some time reviewing the task list that is available in the portal.&lt;/p&gt;

&lt;p&gt;It is essential to understand the basic terms and components of a pipeline. This knowledge is necessary to deliver code more efficiently and in a reliable manner. For brevity I will describe a some of the relevant pipeline concepts below. You can read more about pipeline components &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/get-started/key-pipelines-concepts?view=azure-devops" rel="noopener noreferrer"&gt;here.&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Term / Component&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agents&lt;/td&gt;
&lt;td&gt;Agents effectively are virtual machines with installed agent software that run one job at a time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approvals&lt;/td&gt;
&lt;td&gt;Approvals are a set of validations that are required to pass before a deployment runs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;A collection of resources, i.e., VMs, containers, or other services that are used to host the application. After a completed build, a pipeline may deploy changes to one or more environments.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stage&lt;/td&gt;
&lt;td&gt;A stage is a group of related jobs and steps. Stages run sequentially and can use conditional logic to determine if a stage should execute or be skipped.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Job&lt;/td&gt;
&lt;td&gt;A job is a collection of steps that run together on the same agent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Release&lt;/td&gt;
&lt;td&gt;Branches keep a history of commits and isolate code changes from the main branch. Branches are isolated, so committing changes to a branch doesn't affect other branches.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Step&lt;/td&gt;
&lt;td&gt;A step is the most granular task within a pipeline. It can be either a task or a script.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;A collection of steps that are executed sequentially.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pipelines can include tasks for building out infrastructure, deploying code to cloud services, performing tests, and generating output assets. Outputs are referred to as "Artifacts" and can become input to downstream tasks or processes. Artifacts can be deployed with the application to an environment.&lt;/p&gt;

&lt;p&gt;More information about pipelines can be found &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/?view=azure-devops" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Plans
&lt;/h3&gt;

&lt;p&gt;An important part of the development process is the ability to test and validate the project deliverables. The &lt;code&gt;Test Plans&lt;/code&gt; category offers a central location where the team can coordinate all of its manual test activities, track progress, and gain key insights. Using the &lt;code&gt;Test Plans&lt;/code&gt; toolset, teams can create tests for user stories and run the tests directly on the Kanban board. Creating, configuring, and running tests involves many steps. Review the resources available at this &lt;a href="https://docs.microsoft.com/en-us/azure/devops/test/?view=azure-devops" rel="noopener noreferrer"&gt;link&lt;/a&gt; for a complete guide. For now, let's walk through the basics.&lt;/p&gt;

&lt;p&gt;Test cases are created from work-items off the &lt;code&gt;Board&lt;/code&gt;. This is a bit counterintuitive as you may be inclined to think tests would be created via the &lt;em&gt;Test Plans&lt;/em&gt; link. To create a test, select a work-item from the Board and click on the three dots. This action opens a context menu. Choose &lt;em&gt;Add Test&lt;/em&gt; from the menu as shown in the following screen shot. &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648500362%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsCreateTestPlan_qdwrg2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648500362%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsCreateTestPlan_qdwrg2.png" title="context menu to create test plan" alt="context menu to create test plan"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Existing tests are available from the &lt;em&gt;Test Plans&lt;/em&gt; link. The Test Plans dashboard lists all tests and provides a portal for running and managing tests. &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648501359%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsTestPlanDashboard_eojmuo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648501359%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsTestPlanDashboard_eojmuo.png" title="Test Plans dashboard" alt="Test Plans dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Editing a test is accomplished via the context menu for each test case item. The form used to edit tests is shown below. The form provides places to enter test steps and to create parameters among other things. &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648500813%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsEditTestDialog_ibhuxd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1648500813%2FDev.to%2FAzure%2520DevOps%2520for%2520SpaceLift%2FAzureDevOpsEditTestDialog_ibhuxd.png" title="Edit test dialog" alt="Edit test dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The context menu also provides options for running tests and reviewing test results.&lt;/p&gt;

&lt;p&gt;Robust reporting can be found under the &lt;em&gt;Progress&lt;/em&gt; link. Details like the number of test plans, the percentage of tests that have passed, and the names of specific tests are useful for teams looking to ensure the highest quality software is delivered. Additional information and test executions are available under the &lt;em&gt;Runs&lt;/em&gt; link.&lt;/p&gt;

&lt;h3&gt;
  
  
  Artifacts
&lt;/h3&gt;

&lt;p&gt;Azure &lt;code&gt;Artifacts&lt;/code&gt; provides a mechanism for developers to share and consume packages from different feeds and public registries. DevOps teams can consume packages from well-known package managers like NuGet, and npm, or can create and share their own packages. Sharing can be scoped to the same team, organization, or publicly.&lt;/p&gt;

&lt;p&gt;Depending on usage, teams may incur a cost when using Artifacts. Artifacts is billed on a consumption basis and is free up until 2 GiB of storage. Once the 2GiB storage limit is reached, no new artifacts can be uploaded. For storage beyond 2GiB, a billing account must be setup.&lt;/p&gt;

&lt;p&gt;Packages are organized into "Feeds" which allow the team to group packages and control sharing permissions as a cohesive unit. Public feeds enable packages to be shared publicly with &lt;em&gt;anyone&lt;/em&gt; on the Internet. Literally anyone can access the packages, even if they don't have an Azure DevOps account or are not part of the company. Organization-scoped feeds are only viewable and accessible in the Azure Artifacts hub from projects within your organization. Packages that are shared via a feed are immutable.&lt;/p&gt;

&lt;p&gt;Teams may decide to use Azure Artifacts to consume the artifact in a release, or as part of a deployment the build needs additional packages stored in Azure Artifacts, i.e., NuGet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure DevOps Benefits/Advantages
&lt;/h2&gt;

&lt;p&gt;The value proposition Azure DevOps brings is in its cohesive toolset. Everything a team needs to adopt a DevOps culture can be found within the Azure DevOps services. This is one of its biggest advantages over the alternatives. Other players offer similar features and services, but Azure DevOps brings them all together under one SKU.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure DevOps Alternatives
&lt;/h2&gt;

&lt;p&gt;The list below includes some alternatives to Azure DevOps. This is not a comprehensive list. Many of these players are top alternatives, but none seem to provide the breadth of services that Azure DevOps offers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Provides tools for Git repositories, CI/CD, collaboration, and automation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;td&gt;AWS offers ala carte services for DevOps teams. Included in their offerings is &lt;a href="https://aws.amazon.com/codepipeline/" rel="noopener noreferrer"&gt;AWS CodePipeline.&lt;/a&gt; CodePipeline is a CI/CD service for application and infrastructure. &lt;a href="https://aws.amazon.com/codedeploy/" rel="noopener noreferrer"&gt;AWS CodeDeploy&lt;/a&gt; CodeDeploy is a service which automates deployment to AWS or on-premises infrastructure. &lt;a href="https://aws.amazon.com/codecommit/" rel="noopener noreferrer"&gt;AWS CodeCommit&lt;/a&gt; CodeCommit is a source control solution.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://bitbucket.org/" rel="noopener noreferrer"&gt;BitBucket&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Provides teams one place for source control repositories, project management and deployment.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.jenkins.io/" rel="noopener noreferrer"&gt;Jenkins&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;An open source automation server which uses plugins to support building, deploying and automating any project.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Congratulations, you made it to the end of the article! Along the way you learned what is necessary for a software team to adopt a DevOps culture. Planning, Development, Delivery, and Operating are all key phases of that culture. Azure DevOps provides tools and services for teams to implement DevOps principles into their ALM software development process. In Azure DevOps it begins with the creation of an organization, and projects within organizations. This provides a wealth of tools for teams to use to track progress and report status, manage source control, set up a CI/CD pipeline, perform tests, and monitor the entire process. While alternatives exist, Azure DevOps is a best-in-class set of services and should be a serious consideration for any team looking to adopt a DevOps culture.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>devops</category>
      <category>azure</category>
    </item>
    <item>
      <title>Automate EC2 Instance Shutdown with Auto Scaling Groups to Minimize AWS Costs</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Mon, 18 Oct 2021 12:45:41 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/automate-ec2-instance-shutdown-with-auto-scaling-groups-to-minimize-aws-costs-2fb3</link>
      <guid>https://dev.to/pdelcogliano/automate-ec2-instance-shutdown-with-auto-scaling-groups-to-minimize-aws-costs-2fb3</guid>
      <description>&lt;p&gt;Shutting down EC2 instances when they are not in use is an effective way to manage cloud costs. In my &lt;a href="https://www.cnn.com" rel="noopener noreferrer"&gt;previous post&lt;/a&gt; I described a method for starting and stopping EC2 instances on a schedule using CloudWatch Event Rules and Lambdas. &lt;/p&gt;

&lt;p&gt;Launch Templates, in conjunction with Auto Scaling Groups (ASG), provide another solution which achieves the same result. This solution uses the ASG's ability to scale-out and scale-in on a schedule. More information about the use cases for each solution can be found in the previous post. This post focuses on setting up Launch Templates and ASGs. It assumes you have a general working knowledge of each of these AWS services. &lt;/p&gt;

&lt;h3&gt;
  
  
  Launch Templates and ASG
&lt;/h3&gt;

&lt;p&gt;Employing this method to scale-out and scale-in EC2 instances on a schedule can be broken down into three steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a Launch Template from an EC2 instance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create the Auto Scaling Group&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create schedules to scale the ASG&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 1: Create a Launch Template from an EC2 instance
&lt;/h3&gt;

&lt;p&gt;From the &lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/LaunchTemplates.html" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt;, a launch template &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...specifies instance configuration information. It includes the ID of the Amazon Machine Image (AMI), the instance type, a key pair, security groups, and other parameters used to launch EC2 instances...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Launch Templates are used when scaling EC2 instances with ASG. Whenever an ASG needs to scale-out and create a new EC2 instance, the instance is configure using the settings from the launch template.&lt;/p&gt;

&lt;p&gt;To create a launch template from an existing EC2 instance, navigate to the EC2 services console and click on the "Instances" link. Start the Launch Template wizard by selecting an existing instance and choosing &lt;em&gt;Actions&lt;/em&gt; | &lt;em&gt;Image and templates&lt;/em&gt; | &lt;em&gt;Create template from instance&lt;/em&gt; menu options.&lt;/p&gt;

&lt;p&gt;There are many settings in the wizard. I've listed the relevant settings by section below. Any settings not called out specifically can be left as the default.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Setting Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Launch template name and description&lt;/td&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;"schedule-launch-template"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Auto Scaling guidance&lt;/td&gt;
&lt;td&gt;false (unchecked)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Settings&lt;/td&gt;
&lt;td&gt;Security Groups&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Interfaces&lt;/td&gt;
&lt;td&gt;Subnet&lt;/td&gt;
&lt;td&gt;select "Don't include in launch template"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Security Groups&lt;/td&gt;
&lt;td&gt;none - remove any that are pre-selected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced details&lt;/td&gt;
&lt;td&gt;Request Spot Instances&lt;/td&gt;
&lt;td&gt;false (unchecked)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Shutdown behavior&lt;/td&gt;
&lt;td&gt;select "Don't include in launch template"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After completing the form, click the "Create launch template" button to validate and save the template. A common source of invalid templates is the combination of selected security groups and subnets. If you have an invalid template, be sure to verify you have selected the proper values for those settings. Once the template is valid, you will see a success message similar to the one shown below. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DciV_78E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/redfive/image/upload/v1634313454/Dev.to/EC2-Shutdown/launch-template-created_vv2f1s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DciV_78E--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/redfive/image/upload/v1634313454/Dev.to/EC2-Shutdown/launch-template-created_vv2f1s.png" title="launch template success" alt="launch template success" width="741" height="752"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This dialog presents three links. To start the wizard which guides the creation of the ASG, click on the "Create Auto Scaling group" link. &lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create the Auto Scaling Group
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/AutoScalingGroup.html" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt; describes an ASG as &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;...a collection of Amazon EC2 instances that are treated as a logical grouping for the purposes of automatic scaling and management...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ASGs provide functionality for scaling the number of running instances. It is this feature that makes ASGs ideal for this solution. &lt;/p&gt;

&lt;p&gt;Like the launch template wizard, there are many settings in the ASG wizard. The ASG wizard walks through seven steps, four of which are optional. For brevity's sake, I focused on the values for the required steps and skipped over the optional ones. Any settings not called out specifically can be left as their default unless you have special needs for your unique configuration.&lt;/p&gt;

&lt;p&gt;Step one asks for an ASG name and a Launch Template. Enter "schedule-instance-asg" as the name and select the "schedule-launch-template" Launch Template. Click the "Next" button to navigate to step two.&lt;/p&gt;

&lt;p&gt;The first section in step two asks for the &lt;em&gt;Instance Purchase options&lt;/em&gt;. Instance purchase options provide a way to specify how instances are to be distributed. Select the "Combine purchase options and instance types" option. &lt;/p&gt;

&lt;p&gt;The settings in the &lt;em&gt;Instance Distribution&lt;/em&gt; section allows further cost optimization by providing choices for how EC2 instances are paid. ASGs allow for two distribution options, &lt;em&gt;On-demand&lt;/em&gt; and &lt;em&gt;Spot&lt;/em&gt;. Spot instances are cheaper but have very specific use cases. You can learn more about EC2 pricing &lt;a href="https://aws.amazon.com/ec2/pricing/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Since this is a post about saving costs I decided to use spot instances instead of On-demand. You will have to decide what is best for your situation. To use spot instances provide the following values when filling out the form:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;On-Demand Instances&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;% On-Demand&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;% Spot&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spot allocation strategy per Availability Zone&lt;/td&gt;
&lt;td&gt;select the recommended value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capacity optimized Spot settings&lt;/td&gt;
&lt;td&gt;select Capacity rebalance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;Instance Type&lt;/em&gt; section allows for provisioning the type of VM in the ASG. The instance type value will be pre-selected with the value specified in the launch template that was created in step one. You can modify this setting or leave it as the default. A nice benefit that comes from the scheduling of instances to shut down is that you can choose a beefier VM knowing that you are saving money when the instance shuts down. After selecting the instance type, move onto the &lt;em&gt;Network&lt;/em&gt; section.&lt;/p&gt;

&lt;p&gt;In this section, select the VPC and subnets where the instances are to be launched. The default VPC is pre-selected so be sure to change it if you are using a non-default VPC. That's all for wizard step two. Click the "Next" button until you reach step five where you can add notifications for ASG events. &lt;/p&gt;

&lt;p&gt;SNS Topics are used to send notifications whenever the instances in an ASG are started or stopped. To setup notifications, select an existing topic from the &lt;em&gt;SNS Topic&lt;/em&gt; dropdown, or click the "Create a topic" button to create a new SNS topic. Provide one or more email addresses in the &lt;em&gt;With these recipients&lt;/em&gt; textbox and select one or more of the following ASG events: &lt;em&gt;Launch&lt;/em&gt;, &lt;em&gt;Terminate&lt;/em&gt;, &lt;em&gt;Fail to launch&lt;/em&gt;, &lt;em&gt;Fail to terminate&lt;/em&gt;, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jCap-MaR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/redfive/image/upload/v1634477709/Dev.to/EC2-Shutdown/asg_6_dm1jwa_xpanly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jCap-MaR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/redfive/image/upload/v1634477709/Dev.to/EC2-Shutdown/asg_6_dm1jwa_xpanly.png" title="ASG notifications" alt="ASG notifications" width="744" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "Skip to review" button to jump to the final step in the ASG wizard. Here you can review the settings and configurations chosen in all of the previous steps. After verifying everything, click the "Create Auto Scaling group" button to create the ASG.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create schedules to scale the ASG
&lt;/h3&gt;

&lt;p&gt;The final step in this process is to setup the scale-out and scale-in schedules. Select the "schedule-instance-asg" ASG from the list, click on the "Automatic Scaling" link and scroll down to the &lt;em&gt;Scheduled actions&lt;/em&gt; section. Click "Create scheduled action" to open the form shown below. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VpWt1ydu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/redfive/image/upload/v1634510057/Dev.to/EC2-Shutdown/asg_startup_yttzmj_u0kuzh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VpWt1ydu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/redfive/image/upload/v1634510057/Dev.to/EC2-Shutdown/asg_startup_yttzmj_u0kuzh.png" title="ASG scale out settings" alt="ASG scale out settings" width="539" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use this form to create the schedule that will scale-out, i.e. startup the instance(s). Provide the values from the table below to scale-out to one instance at 7AM Est, Monday through Friday.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;asg-scale-out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desired Capacity&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Min&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recurrence&lt;/td&gt;
&lt;td&gt;Cron&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cron expression&lt;/td&gt;
&lt;td&gt;00 11 * * MON-FRI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time zone&lt;/td&gt;
&lt;td&gt;Etc/UTC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Click the "Create" button to save the schedule. Repeat the process to create the schedule to scale-in, i.e. shutdown, the instance(s). Provide the following values to run the schedule at 8PM Est, Tuesday through Saturday.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;asg-scale-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desired Capacity&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Min&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recurrence&lt;/td&gt;
&lt;td&gt;Cron&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cron expression&lt;/td&gt;
&lt;td&gt;05 00 * * TUE-SAT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time zone&lt;/td&gt;
&lt;td&gt;Etc/UTC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Click the "Create" button to save the scale-in schedule. &lt;/p&gt;

&lt;h3&gt;
  
  
  Logging and Monitoring
&lt;/h3&gt;

&lt;p&gt;One of the features of ASG is its inherent ability to log activity  and monitor instances. You can view the logs by selecting the "schedule-instance-asg" ASG from the list and clicking the "Activity" link. The data shown includes the status and runtime of each ASG. It includes any deltas in the number of running instances among other useful information. &lt;/p&gt;

&lt;p&gt;Instance monitoring is achieved by clicking on the "Monitoring" link. ASGs monitors a wealth of data including Minimum and Maximum Instance counts, the number of instances running and terminated, and resource usage of the EC2 instances in the ASG.&lt;/p&gt;

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

&lt;p&gt;Controlling costs in the cloud can seem like a full time job (actually, it probably is). If left running, EC2 Instances quickly accrue fees and can lead to sticker shock when you get your monthly invoice. Automating the shutdown of EC2 Instances is one very effective method for controlling costs. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.cnn.com" rel="noopener noreferrer"&gt;Part one&lt;/a&gt; of this series focused on using CloudWatch Event Rules and Lambdas to automate EC2 instance startup and shutdown. Here we looked at using Launch Templates and Auto Scaling Groups to achieve the same result. While each has its own pros and cons, both are extremely effective techniques for keeping costs under control. Drop me a note in the comments and let me know how you control costs in your cloud environment using these, or any other methods.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>tutorial</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>Automate EC2 Instance Shutdown with Lambdas to Minimize AWS Costs</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Mon, 18 Oct 2021 12:44:43 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/automate-ec2-instance-shutdown-with-lambdas-to-minimize-aws-costs-5886</link>
      <guid>https://dev.to/pdelcogliano/automate-ec2-instance-shutdown-with-lambdas-to-minimize-aws-costs-5886</guid>
      <description>&lt;p&gt;In the cloud, cost control is a task unto itself. It takes diligent oversight over the services in your AWS cloud to ensure the monthly spend doesn't grow out of control. One method for lowering costs is to shutdown EC2 Instances when they are not in use. In this two-part series, I will show you how to reduce your monthly spend by scheduling the shut down of EC2 Instances using two different methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controlling Costs
&lt;/h3&gt;

&lt;p&gt;There are several factors which determine how an EC2 Instance is billed. The main factor is by usage, typically measured by the hour. As long as an EC2 Instance is in the &lt;em&gt;running&lt;/em&gt; state, you are accruing fees, even if you are not actually using the VM. An effective method for controlling these costs is to shut down the VM when it is not needed. When the instance is in the &lt;em&gt;stopped&lt;/em&gt; or &lt;em&gt;terminated&lt;/em&gt; state you still pay for storage, but that is a fraction of the compute costs you pay while the instance is running.&lt;/p&gt;

&lt;p&gt;Development, QA, or Test environments are all good use cases for automating the shutdown of an EC2 Instance. In these environments, the instances are most active during business hours. Those instances do not need to be running once everyone signs off at the end of the day.&lt;/p&gt;

&lt;p&gt;I use two methods for shutting down EC2 instances. The first involves using CloudWatch Event Rules together with Lambdas. The second uses Launch Templates in conjunction with Auto Scaling Groups (ASG). &lt;/p&gt;

&lt;p&gt;Each method has its own use cases. For simple startup/shutdown scheduling I like the CloudWatch/Lambda solution. This method is the easier of the two to setup. It works well when scheduling a set of EC2 instances. It is also appropriate for use cases involving EC2 instances with static public IP addresses (EIPs).&lt;/p&gt;

&lt;p&gt;The relatively more complex Launch Template/ASG method provides additional functionality beyond scheduling like email notifications, and the ability to continually retry starting an instance if it fails to start. This is a good solution when no EIPs are involved, when all of your EC2 instances are the same instance type, or you want more robust monitoring and notifications.&lt;/p&gt;

&lt;p&gt;I'll walk through both methods for controlling and minimizing cloud costs. This post focuses on using CloudWatch Event Rules and Lambdas to schedule instance startup and shutdown. The &lt;a href="https://dev.to/pdelcogliano/automate-ec2-instance-shutdown-with-auto-scaling-groups-to-minimize-aws-costs-2fb3"&gt;second post&lt;/a&gt; walks through using Launch Templates with ASG to achieve the same result.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Event Rules and Lambda
&lt;/h3&gt;

&lt;p&gt;Among the many AWS services, CloudWatch Events and Lambdas can be combined to provide basic scheduling capabilities. The &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/start-stop-lambda-cloudwatch/" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt; has in-depth instructions for using CloudWatch Events and Lambdas to shutdown and startup EC2 instances on a schedule. &lt;/p&gt;

&lt;p&gt;There are three basic steps to using this technique. The steps below use the AWS Console to setup and configure the policy and role, the CloudWatch Events Rules and Lambda functions. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a custom Identity and Access Management (IAM) policy and execution role for the Lambda functions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create and test two Lambda functions; one to stop EC2 instances, and another to start them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create CloudWatch Event Rules that trigger the Lambda functions on a schedule.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Step 1 - Create IAM Policy and Role
&lt;/h4&gt;

&lt;p&gt;Using the IAM service console, create an IAM policy by clicking on the "Create policy" button. This starts the policy creation wizard. On the JSON tab, enter the policy permissions from Listing 1 below. These permissions allow CloudWatch logging and EC2 startup and shutdown. &lt;/p&gt;

&lt;h6&gt;
  
  
  Listing 1
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"logs:CreateLogStream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"logs:PutLogEvents"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:logs:*:*:*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:Start*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:Stop*"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click through the wizard until you get to the "Review" page. On the Review page, enter "&lt;em&gt;lambda-policy-for-scheduling-ec2-instances&lt;/em&gt;" as the policy name. Click the "Create policy" button to save the policy. The following screenshot shows the policy review screen. Notice that the Summary section shows permissions being applied to CloudWatch Logs and EC2 services.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634134270%2FDev.to%2FEC2-Shutdown%2Fcreate_IAM_policy_ozfro2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634134270%2FDev.to%2FEC2-Shutdown%2Fcreate_IAM_policy_ozfro2.png" title="create policy" alt="create policy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To setup the role, navigate to the Roles link in the IAM Console and click the "Create role" button. This starts the role creation wizard. On the first page, select the &lt;em&gt;AWS service&lt;/em&gt; option and choose the "Lambda" use case. Click the "Next: Permissions" button. Search for the policy created earlier named "&lt;em&gt;lambda-policy-for-scheduling-ec2-instances&lt;/em&gt;", and select it from the list to attach it to the role. Click through the wizard until you get to the &lt;em&gt;Review Role&lt;/em&gt; step. The following screenshot shows the role review screen. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634134270%2FDev.to%2FEC2-Shutdown%2Fcreate_IAM_role_nkxn1k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634134270%2FDev.to%2FEC2-Shutdown%2Fcreate_IAM_role_nkxn1k.png" title="create role" alt="create role"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter "&lt;em&gt;lambda-role-for-scheduling-ec2-instances&lt;/em&gt;" for the role name, then click the "Create role" button to save the role.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 2 - Create two Lambda functions
&lt;/h4&gt;

&lt;p&gt;In the AWS console, open the Lambdas service console. Click the "Create Function" button to start the wizard. Select the &lt;em&gt;Author from scratch&lt;/em&gt; option. Name the function "start-ec2-instances". Select "python 3.9" from the &lt;em&gt;Runtime&lt;/em&gt; dropdown. Click on the "Change default execution role" link and select the &lt;em&gt;Use an existing role&lt;/em&gt; option. Select the role created above, "&lt;em&gt;lambda-role-for-scheduling-ec2-instances&lt;/em&gt;" from the drop down. Leave all other settings as their defaults, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634134270%2FDev.to%2FEC2-Shutdown%2Fcreate_function_ymyntw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634134270%2FDev.to%2FEC2-Shutdown%2Fcreate_function_ymyntw.png" title="lambda wizard" alt="lambda wizard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "Create Function" button to save the function. The next step in the wizard is to provide code for the lambda. The python script in Listing 2 below is used to start your EC2 instances. Copy/paste the script into the lambda wizard's &lt;em&gt;code&lt;/em&gt; tab. &lt;/p&gt;

&lt;h6&gt;
  
  
  Listing 2
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your region here&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;instances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your 1st instance id&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your 2nd instance id&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;ec2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ec2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InstanceIds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;started your instances: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script contains two variables, &lt;code&gt;region&lt;/code&gt; and &lt;code&gt;instances&lt;/code&gt;. Replace &lt;code&gt;&amp;lt;your region here&amp;gt;&lt;/code&gt; with the AWS region name where your EC2 instances are running, i.e. &lt;code&gt;us-east-1&lt;/code&gt;. The &lt;code&gt;instances&lt;/code&gt; variable is a comma separated list of EC2 Instance IDs. Provide one or more instances in the list. Each instance provided will be affected by the function. Navigate to the AWS EC2 console to find your Instance IDs.&lt;/p&gt;

&lt;p&gt;After editing the script, click the "Test" button. This opens a dialog allowing the Lambda to be tested. Provide a value for the test's Event Name and click "Create". Click the "Test" button again to run the test. Review the output, looking for errors. The following screenshot shows the completed Test dialog form.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634134270%2FDev.to%2FEC2-Shutdown%2Ftest_function_yk8ch2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634134270%2FDev.to%2FEC2-Shutdown%2Ftest_function_yk8ch2.png" title="lambda test" alt="lambda test"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of note; testing the Lambda function will execute the python script. Any instances listed in the startup function will be started. The same goes for the shutdown function. Testing that function will shutdown the instances.&lt;/p&gt;

&lt;p&gt;Once any errors and warnings are resolved, repeat the process to create the "stop-ec2-instances" Lambda. The python script in Listing 3 provides the code for stopping EC2 instances. Provide the same values for &lt;code&gt;region&lt;/code&gt; and &lt;code&gt;instances&lt;/code&gt; here as you did for the "start-ec2-instances" script.&lt;/p&gt;

&lt;h6&gt;
  
  
  Listing 3
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your region here&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;instances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your 1st instance id&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your 2nd instance id&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;ec2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ec2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InstanceIds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;stopped your instances: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3 - Create CloudWatch Event Rules
&lt;/h3&gt;

&lt;p&gt;The final step in this process is to setup the schedules that will stop and start the instances. Begin by navigating to the CloudWatch service in the AWS console. Click the Rules link and then the "Create rule" button. This will create a new rule which will be used to schedule instance startup. &lt;/p&gt;

&lt;p&gt;The screenshot below shows the &lt;em&gt;Event Source&lt;/em&gt; section. This is where the schedule is defined. Start by selecting the &lt;em&gt;Schedule&lt;/em&gt; option and the &lt;em&gt;Cron expression&lt;/em&gt; option. Enter the following cron expression to schedule instance startup to occur at 7 AM EST, Monday - Friday: &lt;code&gt;0 11 ? * MON-FRI *&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634158155%2FDev.to%2FEC2-Shutdown%2FCloudWatchRuleStep1_vgeopx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1634158155%2FDev.to%2FEC2-Shutdown%2FCloudWatchRuleStep1_vgeopx.png" title="CloudWatch Rule Step 1" alt="CloudWatch Rule Step 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, click the "Add Target" button. Select &lt;em&gt;Lambda function&lt;/em&gt; from the drop down and select the "&lt;em&gt;start-ec2-instances&lt;/em&gt;" function. Click the "Configure details" button and provide a name for the rule. Click the "Create rule" button to save the rule. &lt;/p&gt;

&lt;p&gt;Repeat this process to configure a rule to shutdown the EC2 instances. The following cron expression schedules instance shutdown for 8:05 PM EST, Tuesday - Saturday: &lt;code&gt;05 00 ? * TUE-SAT *&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Logging
&lt;/h3&gt;

&lt;p&gt;You may have noticed earlier that the policy contained permissions for logging. Every time one of the Lambda functions executes, it logs its output to a CloudWatch Log group. To view the logs, navigate to the "Log groups" link under the CloudWatch console. In the list of log groups, you should see one named "&lt;em&gt;aws/lambda/start-ec2-instances&lt;/em&gt;". This was created when you tested the lambda function. Click that log group and navigate to the log streams where you will find the results of the lambda's execution, including the output from any &lt;code&gt;print&lt;/code&gt; statements in the python script.&lt;/p&gt;

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

&lt;p&gt;Controlling costs in the cloud is a full time job. The combined power of CloudWatch Event Rules and Lambdas provides one method of controlling those costs by scheduling the startup and shutdown of EC2 instances. &lt;a href="https://dev.to/pdelcogliano/automate-ec2-instance-shutdown-with-auto-scaling-groups-to-minimize-aws-costs-2fb3"&gt;In the next post&lt;/a&gt;, we'll look at another approach to cost cutting using Launch Templates and Auto Scaling Groups. See you there.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>tutorial</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>AWS Subnet Tip: Using the Auto-Assign Attribute</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Mon, 13 Sep 2021 13:19:14 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/aws-subnet-tip-using-the-auto-assign-attribute-51al</link>
      <guid>https://dev.to/pdelcogliano/aws-subnet-tip-using-the-auto-assign-attribute-51al</guid>
      <description>&lt;p&gt;Have you ever launched an EC2 instance into a subnet only to discover your instance doesn't have a public IPv4 address? Your subnet configuration may be the reason for this issue. In this quick tip, I will explain why this happens and show you how to control this behavior.&lt;/p&gt;

&lt;p&gt;When launching an EC2 instance from the AWS portal, you need to specify how a public IP address gets assigned to the instance. There are a few options, but the one I want to focus on here allows AWS to auto-assign an IPv4 address. This option pulls an IP address from Amazon's public IP address pool and assigns it to your instance.&lt;/p&gt;

&lt;p&gt;The auto-assign option is set via the launch wizard's &lt;em&gt;auto-assign Public IP&lt;/em&gt; setting, as shown in the image below. There are three values to choose from, "Use subnet setting", "enable", or "disable". &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0quv04en8y6amcr7esaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0quv04en8y6amcr7esaz.png" alt="EC2 IPv4 launch setting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "enable" and "disable" values do exactly what you would expect; enable or disable the auto-assign functionality. Disabling the auto-assign property is useful when the EC2 instance shouldn't be publicly available or maybe you will assign an Elastic IP (EIP) address to the instance. Choosing the "Use subnet setting" can lead to an instance that isn't available publicly over the Internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  It's All About the Subnet
&lt;/h3&gt;

&lt;p&gt;Notice in the image below that subnets have a property named &lt;em&gt;Enable auto-assign public IPv4 address&lt;/em&gt;. This property configures the &lt;em&gt;subnet's&lt;/em&gt; auto-assign behavior. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qzu6zf1clqk3w7sjl5u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qzu6zf1clqk3w7sjl5u.png" alt="AWS subnet auto-assign setting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An instance launched with the "Use subnet setting" value instructs AWS to apply the IP address assignment behavior as configured at the subnet level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subnet Behavior
&lt;/h3&gt;

&lt;p&gt;Subnets created by AWS are called default subnets. These subnets have their auto-assign property set to &lt;em&gt;true&lt;/em&gt; by default. Subnets you create, called non-default subnets, set the property's value to &lt;em&gt;false&lt;/em&gt; by default. The one exception to this rule is a subnet created by the Instance Launch Wizard. The wizard sets the auto-assign property to &lt;em&gt;true&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you select the default subnet at the time of instance creation and choose the "Use subnet setting" option, the instance will have a public IPv4 address assigned. However, if you choose a non-default subnet, that instance may not get a public IP address. It all depends on how you configured your subnet to use the auto-assign functionality.&lt;/p&gt;

&lt;p&gt;AWS provides an API to modify the subnet's auto-assign property. You can use the AWS CLI to enable or disable the property.&lt;/p&gt;

&lt;p&gt;To enable auto-assign:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

aws ec2 modify-subnet-attribute --subnet-id &amp;lt;your-subnet-id&amp;gt; --map-public-ip-on-launch


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

&lt;/div&gt;

&lt;p&gt;To disable auto-assign:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

aws ec2 modify-subnet-attribute --subnet-id &amp;lt;your-subnet-id&amp;gt; --no-map-public-ip-on-launch


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

&lt;/div&gt;

&lt;p&gt;Changing the value does not affect existing instances. It only applies to future instances created within the subnet. The AWS portal can be used to modify this setting as well. &lt;/p&gt;

&lt;p&gt;In this quick tip I wanted to point out the behavior of the auto-assign property, both at the instance, and more so at the subnet level. Not enabling the auto-assign option at the subnet level may lead to the creation of an EC2 instance that doesn't have a public IP address, forcing you to recreate the instance after you've modified the subnet properties.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>network</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Use Terraform Workspaces for Multi-Environment Deployments</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Tue, 31 Aug 2021 15:12:02 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/use-terraform-workspaces-for-multi-environment-deployments-1i7a</link>
      <guid>https://dev.to/pdelcogliano/use-terraform-workspaces-for-multi-environment-deployments-1i7a</guid>
      <description>&lt;p&gt;Over the course of my brief time with Terraform, I've come to appreciate its power and relative ease of use over other Infrastructure as Code (IaC) tools. As an IaC noob, I have had to overcome many challenges that novices have stumbled over. &lt;/p&gt;

&lt;p&gt;One such block I stumbled over involved applying the same configuration to different environments, i.e. dev, prod. Under certain conditions, when you apply the same configuration to multiple environments Terraform first destroys the previous resources before applying the changes to the new environment. &lt;/p&gt;

&lt;p&gt;This can happen when you use variables as part of a resource name. Terraform sees changes to resource names as a reason to destroy a resource. These types of changes are destructive because you can't modify a resource's name in AWS. Instead, you have to delete and recreate the resource. Terraform is simply following the AWS rules.&lt;/p&gt;

&lt;p&gt;In my case, I was naming my resources with the environment name appended to the resource name via a string interpolation template like &lt;code&gt;user_${var.environment}&lt;/code&gt; This would give me names like "user_dev" or "user_prod". Terraform picked up the change in name and marked the IAM user resource for destruction when I ran the configuration for different environments.&lt;/p&gt;

&lt;p&gt;I came to understand how Terraform stores this information in "state". I learned how Terraform Workspaces could be used to overcome this behavior. In this post I am going to describe what I learned and will demonstrate one method you can leverage using Workspaces to build your infrastructure in AWS for multiple environments. &lt;/p&gt;

&lt;h3&gt;
  
  
  A State of Destruction
&lt;/h3&gt;

&lt;p&gt;Before diving into Workspaces, it's important to set some background about how Terraform saves configuration state. &lt;/p&gt;

&lt;p&gt;Terraform stores data about a configuration in a local file named "terraform.tfstate". Terraform stores data like the values from outputs, dependencies, secrets, and AWS IDs of the resources it creates. As a side note, sensitive data is stored in plain text in the state file. You should protect those values at all costs. I'll do a future post talking about securing state data and state management.&lt;/p&gt;

&lt;p&gt;Terraform uses state during subsequent &lt;code&gt;apply&lt;/code&gt; operations to compare the previous plan to the current one and determine any deltas. Examining the deltas, Terraform knows whether or not it can update a resource, or if it must destroy the resource in order to apply the change. &lt;/p&gt;

&lt;p&gt;When I was applying my configuration to multiple environments using variables, Terraform was capturing all changes to a single state file. So when the user name changed from user_dev to user_prod, Terraform saw that as a change to the user_dev resource and marked it for destruction. In actuality, what I wanted it do was create a new account named user_prod and not modify the existing user_dev account.&lt;/p&gt;

&lt;p&gt;What I needed was a way to store state for each environment separately. This would allow me to apply my configuration to each environment independent of the others, and avoid the destruction of resources created in other environments. This is where Workspaces enter the picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter the Workspace
&lt;/h3&gt;

&lt;p&gt;Terraform uses the concept of a Workspace for isolating state. When you initialize a configuration, Terraform creates a default Workspace named, "default". Configuration plans, and other data, are stored in the Workspace. &lt;/p&gt;

&lt;p&gt;You can have multiple &lt;em&gt;named&lt;/em&gt; Workspaces in Terraform. Each Workspace maintains its own state. This allows for multiple states to be associated with a single configuration. Knowing this, I set up one Workspace for each environment. Now I could apply the same configuration to different environments without affecting any other environments!&lt;/p&gt;

&lt;p&gt;To create and manage a Workspace, use the &lt;code&gt;terraform workspace&lt;/code&gt; set of commands as shown in the table below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform workspace new &amp;lt;workspace name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;creates and switches to the newly created workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform workspace select &amp;lt;workspace name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;switches to the workspace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terraform workspace list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;lists workspaces and highlights the active one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's use the following simple configuration to see Workspaces in action. The configuration below creates a read-only policy for all S3 buckets. It defines one variable named "environment", whose default value is "dev". The variable value is appended to the policy name to create a unique policy per environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 3.27"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 0.14.9"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-2"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy"&lt;/span&gt; &lt;span class="s2"&gt;"s3_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"s3_policy_for_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s2"&gt;"Version"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"Statement"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Effect"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Action"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"s3:GetObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;"Resource"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply this configuration using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates the policy in AWS and names it "s3_policy_for_dev". Let's run it again, but this time provide a different value for the environment variable, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt; &lt;span class="nx"&gt;-var&lt;/span&gt; &lt;span class="s2"&gt;"environment=prod"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our intention is to create a new policy named "s3_policy_for_prod", but as you can see from the command's output, Terraform sees the name change and decides it needs to replace the "s3_policy_for_dev" policy and create a new policy.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1630413633%2FDev.to%2FTerraform-Workspaces%2FTerraform1_svyq1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1630413633%2FDev.to%2FTerraform-Workspaces%2FTerraform1_svyq1x.png" title="Terraform output from apply command" alt="Terraform output"&gt;&lt;/a&gt;&lt;br&gt;
Let's resolve this by creating a Workspace for the dev environment. Run the following command to create a Workspace named "dev"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;workspace&lt;/span&gt; &lt;span class="nx"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1630414023%2FDev.to%2FTerraform-Workspaces%2FTerraform2_trrdxk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1630414023%2FDev.to%2FTerraform-Workspaces%2FTerraform2_trrdxk.png" title="Terraform output after creating a Workspace" alt="Terraform output"&gt;&lt;/a&gt;&lt;br&gt;
Reviewing the output you notice Terraform created and switched to a new Workspace named "dev". More importantly the output states, "...Terraform will not see any existing state for this configuration". Our changes to the dev environment will now be stored within the dev Workspace.&lt;/p&gt;

&lt;p&gt;Remember what I said earlier about Workspaces having their own state? The dev Workspace is unaware of the previous &lt;code&gt;apply&lt;/code&gt; command we executed. If you run the &lt;code&gt;terraform plan&lt;/code&gt; command in the dev Workspace, the output will show you that Terraform is going to attempt to create a resource named "s3_policy_for_dev". This will fail upon execution because there is already a resource using that name in AWS.&lt;/p&gt;

&lt;p&gt;Using the AWS portal, go ahead and delete the "s3_policy_for_dev" policy from AWS, then run the &lt;code&gt;terraform apply&lt;/code&gt; command from the dev Workspace. This will synchronize the Workspace state with the resources in AWS.&lt;/p&gt;

&lt;p&gt;Now, let's create the Workspace for our production configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;workspace&lt;/span&gt; &lt;span class="nx"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the policy for the production environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt; &lt;span class="nx"&gt;-var&lt;/span&gt; &lt;span class="s2"&gt;"environment=prod"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, the Terraform output shows one new resource will be created and no other changes will be made. No resources will be destroyed because the separate state file doesn't know about the resources created in the dev Workspace.&lt;/p&gt;

&lt;p&gt;Review your policies in AWS via the portal. You should see two policies, one named "s3_policy_for_dev" and the other named "s3_policy_for_prod".&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1630415315%2FDev.to%2FTerraform-Workspaces%2FTerraformTwoPolicies_sjdau6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1630415315%2FDev.to%2FTerraform-Workspaces%2FTerraformTwoPolicies_sjdau6.png" title="Two AWS policies" alt="Two AWS policies"&gt;&lt;/a&gt;&lt;br&gt;
In addition, if you review your local folder structure where your Terraform files are located, you'll see a folder named terraform.tfstate.d. Expand that folder to find subfolders, one for the dev Workspace and one for prod. Within each subfolder you will find the local state file associated with its parent Workspace.&lt;/p&gt;

&lt;p&gt;Issue the &lt;code&gt;terraform workspace list&lt;/code&gt; command to show all of the available Workspaces.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1630421204%2FDev.to%2FTerraform-Workspaces%2FTerraformList_eghqjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fredfive%2Fimage%2Fupload%2Fv1630421204%2FDev.to%2FTerraform-Workspaces%2FTerraformList_eghqjr.png" title="Listing Workspaces" alt="Terraform list command"&gt;&lt;/a&gt;&lt;br&gt;
The Workspace marked with the asterisk is the currently selected Workspace. Issue the following command to switch back to the dev Workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;terraform&lt;/span&gt; &lt;span class="nx"&gt;workspace&lt;/span&gt; &lt;span class="nx"&gt;select&lt;/span&gt; &lt;span class="nx"&gt;dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Terraform Workspaces help you to navigate issues that may arise from having a single state store. Isolating state within a Workspace provides additional options for multi-environment deployments. I recommend you use Workspaces in scenarios like these or perhaps segmenting your deployments by categories, i.e. networking, security, auditing, etc. Hit me up in the comments and let me know how you apply Workspaces to your configurations...and stay tuned for a post discussing state management.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>cloud</category>
      <category>aws</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Improve Your Web API with Swagger Documentation</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Mon, 15 Mar 2021 17:13:30 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/improve-your-web-api-with-swagger-documentation-1j83</link>
      <guid>https://dev.to/pdelcogliano/improve-your-web-api-with-swagger-documentation-1j83</guid>
      <description>&lt;p&gt;This post is one in a series of posts I am using to further enhance my development skillset. In this post, I will describe how to improve the Mural API &lt;a href="https://github.com/pdelcogliano" rel="noopener noreferrer"&gt;(my reference application used for educational purposes)&lt;/a&gt; using Swagger documentation. Swagger is a set of tools based upon the OpenAPI Specification (OAS) which are used for documenting Web APIs. You may be wondering why documenting your API is necessary. Well, good documentation contributes to the overall user experience and is one of the biggest factors for increased API growth and usage. &lt;/p&gt;

&lt;p&gt;ASP.Net Core uses Swashbuckle, which is an open-source Swagger implementation used for generating API documentation. Through Swashbuckle you will generate living documentation every time you build your API, keeping the documentation in sync with the latest version of your API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Swagger Documentation
&lt;/h3&gt;

&lt;p&gt;The first thing we need to do is install Swashbuckle into the API project. Swashbuckle is available as a NuGet package. I am using PowerShell to install the NuGet package.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Install-Package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Swashbuckle.AspNetCore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;5.6.3&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Swagger functionality is injected as middleware into the ASP.Net pipeline. Open the API project's Startup.cs file and edit the &lt;code&gt;ConfigureServices()&lt;/code&gt; method. Add the line as shown below.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSwaggerGen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SwaggerDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Mural"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v1"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;While still editing the Startup.cs file, locate the &lt;code&gt;Configure()&lt;/code&gt; method and enable middleware for the Swagger UI by adding &lt;code&gt;useSwagger&lt;/code&gt; and &lt;code&gt;useSwaggerUI&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IApplicationBuilder&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IWebHostEnvironment&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSwagger&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSwaggerUI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SwaggerEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/swagger/v1/swagger.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Mural V1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;/// rest of method body&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Save the Startup.cs and run the project. Swashbuckle includes an implementation of the Swagger UI, which is a Web page that provides interaction and visualization to an API's resources. Navigate to the Swagger UI page by going to localhost:44333/swagger/index.html. Note, your local port may vary. When you open this page you will see the basic documentation that comes out of the box from Swagger.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aecdao8a1k6kjcwya8c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3aecdao8a1k6kjcwya8c.png" alt="Basic Swagger Documentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice! With just a few lines of code and a NuGet package, we have a documented API. But, we can enhance the documentation with additional information, like examples for calling the API methods and descriptions of the method parameters. In the next section, we'll enrich the documentation to create an even better user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Providing Enhanced Documentation
&lt;/h3&gt;

&lt;p&gt;We can enrich the API's documentation by including XML comments in the code. You place these comments directly before the code block about which you are commenting. The Swashbuckle tooling automatically includes XML comments in its documentation and makes them available to view via the Swagger UI Web page.&lt;/p&gt;

&lt;p&gt;The first thing we need to do is to enable XML Comments in the project. XML comments can be enabled via several approaches. I chose to use the Project Properties Dialog as shown below. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2txxz4mt95ix86ez881q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2txxz4mt95ix86ez881q.png" alt="Enable XML Comments via Project Properties Dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the dialog, navigate to the "Build" tab and click on the checkbox next to "XML Documentation file". Provide a name for the file, such as "M-url.Api.xml". Of note, when you enable XML Documentation, your code will generate warnings for any method that does not contain XML comments. If you want to disable this compiler warning, include &lt;code&gt;1591&lt;/code&gt; in the Suppress warnings textbox.&lt;/p&gt;

&lt;p&gt;Next, we need to add code to the Startup.cs file to inform Swagger where it can find the XML comments file. Replace the call to &lt;code&gt;AddSwaggerGen&lt;/code&gt; we added earlier with the code shown below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSwaggerGen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SwaggerDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Mural"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v1"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// generate the XML docs that'll drive the swagger docs&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;xmlFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$" 
&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExecutingAssembly&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="s"&gt;";
&lt;/span&gt;    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;xmlPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xmlFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IncludeXmlComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xmlPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now we'll add comments to the code. We'll start identifying which content-types the API handles on requests and responses. You use the &lt;code&gt;Produces&lt;/code&gt; and &lt;code&gt;Consumes&lt;/code&gt; attributes on the controller class to specify the content types allowed by the API. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Produces&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaTypeNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;MediaTypeNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Xml&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Consumes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaTypeNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;MediaTypeNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Xml&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlugCollectionsController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;In the example above, the SlugsController accepts and returns both JSON and XML. This information will appear in the Swagger documentation.&lt;/p&gt;

&lt;p&gt;Here I've added comments to the &lt;code&gt;GetSlugCollection()&lt;/code&gt; method:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// Returns a collection of URLs&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;param name="slugs"&amp;gt;list of slugs to retrieve&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// Sample request:&lt;/span&gt;
&lt;span class="c1"&gt;///&lt;/span&gt;
&lt;span class="c1"&gt;///     Get /api/slugscollection/(d25tRx, fN5jpz)&lt;/span&gt;
&lt;span class="c1"&gt;///&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;returns&amp;gt;IEnumerable of slugs&amp;lt;/returns&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;response code="200"&amp;gt;If all requested items are &lt;/span&gt;
&lt;span class="c1"&gt;/// found&amp;lt;/response&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;response code="400"&amp;gt;If slugs parameter is missing&amp;lt;/response&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;response code="404"&amp;gt;If number of records found doesn't equal &lt;/span&gt;
&lt;span class="c1"&gt;/// number of records requested&amp;lt;/response&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"({slugs})"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"GetSlugCollection"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ProducesResponseType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status200OK&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ProducesResponseType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status400BadRequest&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ProducesResponseType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status404NotFound&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetSlugCollection&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;ModelBinder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BinderType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ArrayModelBinder&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slugs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;While many of the comments are self-explanatory, I do want to highlight a few of them. The following table details the interaction between each XML comment attribute and its effect on the Swagger documentation.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comment Attribute&lt;/th&gt;
&lt;th&gt;Swagger UI Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;summary&lt;/td&gt;
&lt;td&gt;displays text next to the action method name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;remarks&lt;/td&gt;
&lt;td&gt;Supplements information specified in the  element. Can consist of text, JSON, or XML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;response&lt;/td&gt;
&lt;td&gt;Renders as an example HTTP request in the Swagger documentation.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Looking at the code snippet above, you'll notice the additional attribute, &lt;code&gt;ProducesResponseType&lt;/code&gt; has been added to the method. ProducesResponseType informs Swagger of the HTTP Status codes returned by the method. The &lt;code&gt;response&lt;/code&gt; XML comment works in conjunction with the ProducesResponseType attribute to provide descriptive information for each HTTP status code the method returns. &lt;/p&gt;

&lt;p&gt;Alternatively, in place of specifying each individual status code using ProducesResponseType, you can use the &lt;code&gt;ApiConventionMethod&lt;/code&gt; attribute to specify 200, 400, and 404 status codes if your API produces a standard set of response codes. Here's an example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ApiConventionMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DefaultApiConventions&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The final version of the GetSlugCollection method's documentation is below. You can see the additional descriptions next to the parameters, the HTTP status codes being returned, and an example of calling the API using a GET HTTP method.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvr7wv5vvsre6t2cp8d3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvr7wv5vvsre6t2cp8d3.png" alt="Enhanced Mural Documentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is one more change we can do to make the documentation complete. Back in the Startup.cs file, replace the call to AddSwaggerGen we modified earlier with the souped-up version shown below:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;

&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSwaggerGen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SwaggerDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiInfo&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Mural API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Contact&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiContact&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Paul Delcogliano"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"pdelco@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://github.com/pdelcogliano"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;License&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenApiLicense&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"MIT License"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://opensource.org/licenses/MIT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// generate the XML docs that'll drive the swagger docs&lt;/span&gt;
  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;xmlFile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$" 
&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetExecutingAssembly&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="s"&gt;";
&lt;/span&gt;  &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;xmlPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BaseDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xmlFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IncludeXmlComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xmlPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;In this version, we are using the &lt;code&gt;OpenApiInfo()&lt;/code&gt; method to provide additional data for the document header. We are also using the &lt;code&gt;OpenApiLicense()&lt;/code&gt; method to provide information about the API license. Here's the final Swagger documentation, shown in all of its glory with enhanced header information, and enriched method descriptions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgr43kc1jzb3rpml1tbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgr43kc1jzb3rpml1tbw.png" alt="Completed Mural API Documentation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Documenting an ASP.Net Core API using Swashbuckle and Swagger helps your API meet the goals of good documentation. Good documentation provides many benefits. It helps internal teams understand the API and agree on its attributes. It facilitates external clients' understanding of the API and the functionality it provides. More information about getting started with Swagger and Swashbuckle can be found &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-5.0&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>swagger</category>
      <category>tutorial</category>
      <category>aspnetcore</category>
    </item>
    <item>
      <title>Building Your First API with Asp.Net Core</title>
      <dc:creator>Paul Delcogliano</dc:creator>
      <pubDate>Mon, 15 Mar 2021 14:10:13 +0000</pubDate>
      <link>https://dev.to/pdelcogliano/building-your-first-api-with-asp-net-core-1db2</link>
      <guid>https://dev.to/pdelcogliano/building-your-first-api-with-asp-net-core-1db2</guid>
      <description>&lt;p&gt;This is an introduction to a series of posts I am writing. The series describes how to build an API with ASP.Net Core. The series' purpose is to help me while I learn new technologies and to share what I learn with others.&lt;/p&gt;

&lt;p&gt;I believe I have a unique perspective to share. You see, I've been a developer for a long time, but I haven't kept up with technology trends over the past several years. I am now starting a journey to refresh my skillset. In this series, I will apply all I've learned as a "maintenance developer" to today's newer frameworks and technologies, like ASP.Net Core, git, Azure, security, and many more. &lt;/p&gt;

&lt;p&gt;My learning tool will be an API I created, and loving call, "Mural". You can find information about Mural &lt;a href="https://dev.to/pdelcogliano/a-reference-application-for-building-your-first-api-with-asp-net-core-dl9"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Mural is a simple API that creates short URLs from deep links. I intend to apply today's best practices to Mural to create a full feature, secure, scalable, and cloud-ready API. You can download Mural from &lt;a href="https://github.com/pdelcogliano" rel="noopener noreferrer"&gt;my GitHub&lt;/a&gt; and follow along my journey with me.&lt;/p&gt;

</description>
      <category>api</category>
      <category>tutorial</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
