<?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: Alexey Ryazhskikh </title>
    <description>The latest articles on DEV Community by Alexey Ryazhskikh  (@musukvl).</description>
    <link>https://dev.to/musukvl</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%2F513779%2F06f86135-9e13-433c-b8cb-b70d9dc49c89.jpeg</url>
      <title>DEV Community: Alexey Ryazhskikh </title>
      <link>https://dev.to/musukvl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/musukvl"/>
    <language>en</language>
    <item>
      <title>Using GitHub Copilot for Terraform code refactoring</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Sun, 23 Nov 2025 18:36:19 +0000</pubDate>
      <link>https://dev.to/musukvl/using-github-copilot-for-terraform-code-refactoring-1c44</link>
      <guid>https://dev.to/musukvl/using-github-copilot-for-terraform-code-refactoring-1c44</guid>
      <description>&lt;p&gt;Terraform and OpenTofu are the only major IaC tools today that provide a fully deterministic, declarative, provider-agnostic, predictable execution plan across AWS + Azure + other cloud platforms.&lt;/p&gt;

&lt;p&gt;However, the Terraform development experience is still close to what we had for JavaScript in 2008: we have syntax highlighting, basic static analysis, and basic code navigation. But the Terraform code refactoring still should be done manually. We still have no button "extract resources to the module" in JetBrains IDE or VS Code. Mainly because in the Terraform world, code refactoring should go together with state transformation.&lt;/p&gt;

&lt;p&gt;By the end of 2024 in my team had written a lot of Terraform code. We have learned how to master infrastructure as code using Terraform, our expertise has grown up and the code written two years ago become ugly to us. But state transformations and the complexity of code change stopped us from starting to do refactoring on a large scale. For example, we have states with 6K resources.&lt;/p&gt;

&lt;p&gt;We tried to use multiple ways to do state transformations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Created Python scripts to generate Terraform moved blocks for the particular refactoring.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tried to use Terraform file templates and construct object maps to pass old and new resource structure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All these approaches worked, but required a lot of effort, especially for huge states and code base.&lt;br&gt;
Everything changed when GitHub Copilot introduced Edits mode. We started actively using it for Terraform code refactoring, and even then, I realized that Copilot can solve our code quality issues faster.&lt;br&gt;
GitHub Copilot Agents automated the refactoring now we use the following algorithms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ask Copilot for the Terraform code changes.&lt;/strong&gt; Here is very important to limit the change scope and do refactoring fraction by fraction. Better to have multiple iterations for refactoring to not lose control on changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The plan for the small changes is much easier to review.&lt;/strong&gt; The most frequent refactoring we do are to extract resources to the module and change the module structure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Run plan and save output to the file &lt;code&gt;terraform plan -no-color &amp;gt; plan.txt&lt;/code&gt;.&lt;/strong&gt; We need the plan saved to add it to the context. In Agent mode Copilot also able to use shell commands like grep to analyze a plan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generate plan summary and validate if plan meet refactoring requirements.&lt;/strong&gt; We use that step if we need to work with huge states. If the plan contains 500 changed resources, it is difficult to say if the change is valid. The plan summary allows for reviewing types of affected resources and correlating them with the initial change.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Generate moved.tf file with moved blocks to avoid any changes in the next plan.&lt;/strong&gt; On this step, the agent can do multiple iterations for moving blocks generation and runing  the Terraform plan. I would recommend to review moved.tf file because in complex cases LLM could do a wrong correlation between resources and generate move valid from Terraform point of view, but not from the purpose. For example:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;team_two_user&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"team1"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;team_one_user&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"team2"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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




&lt;/li&gt;

&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;In 2025, Terraform remains the strongest cloud‑agnostic Infrastructure‑as‑Code tool. Pulumi is easier to develop with, but it is not as deterministic as Terraform’s declarative model. &lt;br&gt;
Modern AI assistants like GitHub Copilot and Cursor dramatically accelerate safe refactoring and reduce the fear of touching legacy state.&lt;br&gt;
With the help of AI‑driven tooling, Terraform has become a more powerful IaC solution than at any previous point in its history.&lt;/p&gt;

</description>
      <category>terraform</category>
    </item>
    <item>
      <title>Multi-environment infrastructure with terraform variables files</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Sat, 23 Nov 2024 18:36:19 +0000</pubDate>
      <link>https://dev.to/musukvl/multi-environment-with-terraform-variables-file-16bp</link>
      <guid>https://dev.to/musukvl/multi-environment-with-terraform-variables-file-16bp</guid>
      <description>&lt;p&gt;In our company we have thousands of resources managed by Terraform. Which are deployed to multiple environments (dev, staging, production) and different regions.&lt;/p&gt;

&lt;p&gt;The key principles we have for our Terraform codebase are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use the same Terraform codebase (.tf files) for all environments (dev, stage, prod).&lt;/li&gt;
&lt;li&gt;All environment specific settings should be managed via Terraform variable files (.tfvars).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below is an our typical Terraform codebase structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── environments/
│   ├── dev.tfvars
│   ├── stage.tfvars
│   └── prod.tfvars
├── variables.tf
├── db_server.tf
├── main.tf
├── terraform.tf
├── providers.tf
└── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;variables.tf&lt;/code&gt; file contains the variables definitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"resource_group_name"&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;"The resource group name to deploy db server"&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="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"location"&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;"The db server location"&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="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"enable_replication"&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;"Enable DB replication of resources to other region"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;src/environments/dev.tfvars&lt;/code&gt; contains the environment specific settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rg-dev"&lt;/span&gt;
&lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eastus"&lt;/span&gt;
&lt;span class="nx"&gt;enable_replication&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each environment has corresponding terraform state. So we need to specify the state file and &lt;code&gt;.tfvars&lt;/code&gt; file to run &lt;code&gt;terraform apply&lt;/code&gt; command for specific environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"src/environments/dev.tfvars"&lt;/span&gt; &lt;span class="nt"&gt;-state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"dev.tfstate"&lt;/span&gt;
terraform apply &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"src/environments/stage.tfvars"&lt;/span&gt; &lt;span class="nt"&gt;-state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"stage.tfstate"&lt;/span&gt;
terraform apply &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"src/environments/prod.tfvars"&lt;/span&gt; &lt;span class="nt"&gt;-state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"prod.tfstate"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use terraform cloud, you probably need to specify workspace name with &lt;code&gt;TF_WORKSPACE&lt;/code&gt; environment variable instead of state file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Feqfwaxj06n740doyjlpc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Feqfwaxj06n740doyjlpc.png" alt="Terraform Variables and State Flow" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Feature flags
&lt;/h1&gt;

&lt;p&gt;The Terraform variables file can also store feature flags together with terraform modules.&lt;/p&gt;

&lt;p&gt;This approach allows to test the Terraform code in the dev and stage environment before it is applied to other environments. If staging and production environments have the same settings we can have the same code coverage for production.&lt;/p&gt;

&lt;p&gt;Terraform has no if-else logic, so the only way to implement feature flags is to use &lt;code&gt;for_each&lt;/code&gt; and &lt;code&gt;count&lt;/code&gt; statements. &lt;/p&gt;

&lt;p&gt;In the following example we create an azure resource group and role assignments if the &lt;code&gt;enable_replication&lt;/code&gt; variable is &lt;code&gt;true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"enable_replication"&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;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"replica_rg"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_replication&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;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"replica-rg"&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;var&lt;/span&gt;&lt;span class="p"&gt;.&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_role_assignment"&lt;/span&gt; &lt;span class="s2"&gt;"role_assignments"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_replication&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;scope&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;replica_rg&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;id&lt;/span&gt;
  &lt;span class="nx"&gt;role_definition_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Contributor"&lt;/span&gt;
  &lt;span class="nx"&gt;principal_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"12345678-1234-1234-1234-123456789"&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is not perfect, because count condition should be added to each dependent resource. Better to group dependent resources into the local module, to have a single count condition for entire module. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"enable_replication"&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;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"replica_rg"&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;"./modules/replica_rg"&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;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_replication&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;rg_name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"replica-rg"&lt;/span&gt;
  &lt;span class="nx"&gt;contributor_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"12345678-1234-1234-1234-123456789"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Terraform variables as DSL
&lt;/h1&gt;

&lt;p&gt;The terraform variables definitions becomes another layer of abstraction: instead of defining particular resources we define business entities and feature settings. &lt;br&gt;
In fact, &lt;code&gt;.tfvars&lt;/code&gt; files management becomes programming on DSL language defined by terraform variables blocks..&lt;br&gt;
For example, the definition for CI/CD build agents: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;variables.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"build_agents"&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;"Build agents settings"&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;number_of_vms&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;
    &lt;span class="nx"&gt;vm_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;optional&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="s2"&gt;"Standard_N2_v2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;private_network_access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;optional&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# limit access to agent vms&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;&lt;code&gt;dev.tfvars&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;build_agents&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;    
    &lt;span class="nx"&gt;build_pool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;number_of_vms&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
        &lt;span class="nx"&gt;vm_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard_D2_v2"&lt;/span&gt;
        &lt;span class="nx"&gt;private_network_access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;deployment_pool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;number_of_vms&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
        &lt;span class="nx"&gt;vm_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard_D2_v2"&lt;/span&gt;
        &lt;span class="nx"&gt;private_network_access&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are not defining azure resources, but our infrastructure assets, which, in fact could be implemented differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control interface
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;.tfvars&lt;/code&gt; approach allows to define the control interface for infrastructure operators.&lt;br&gt;
So the settings in &lt;code&gt;.tfvars&lt;/code&gt; are working like control panel in pilot cockpit hiding the underlying resources and their dependencies. &lt;br&gt;
For example, if ci/cd admin needs to increase number of agents he don't need to search for resources he needs to reconfigure in terraform codebase. He just changes the &lt;code&gt;number_of_vms&lt;/code&gt; in &lt;code&gt;.tfvars&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refactoring
&lt;/h2&gt;

&lt;p&gt;Naming for the terraform variables and object properties is a challenge. &lt;br&gt;
Time to time we need to do refactoring of variables to change objects structure, or introduce the new properties for all objects. Which also leads changes in all &lt;code&gt;.tfvars&lt;/code&gt; files and states.&lt;/p&gt;

&lt;p&gt;Normally, such refactoring has the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Modifying variables definitions in &lt;code&gt;variables.tf&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Modifying all &lt;code&gt;.tfvars&lt;/code&gt; files.&lt;/li&gt;
&lt;li&gt;Modifying terraform code to support the changes.&lt;/li&gt;
&lt;li&gt;Generating &lt;a href="https://developer.hashicorp.com/terraform/language/moved" rel="noopener noreferrer"&gt;moved blocks&lt;/a&gt; in terraform code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For &lt;code&gt;.tfvars&lt;/code&gt; modification and code generation you can  use python libraries like&lt;br&gt;
&lt;a href="https://pypi.org/project/python-hcl2" rel="noopener noreferrer"&gt;python-hcl2&lt;/a&gt;.&lt;br&gt;
Unfortunately, hcl2 parsers are not available for many other languages, so previously I converted &lt;code&gt;.tfvars&lt;/code&gt; to json and used json as an intermediate format.&lt;br&gt;
I used this go application which is a wrapper over official Hashicorp hcl2 go library: &lt;a href="https://github.com/musukvl/tfvars-parser" rel="noopener noreferrer"&gt;https://github.com/musukvl/tfvars-parser&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Recently I created my own C# dotnet library to work with &lt;code&gt;.tfvars&lt;/code&gt; files: &lt;a href="https://github.com/musukvl/amba-tfvars" rel="noopener noreferrer"&gt;amba-tfvars&lt;/a&gt;. &lt;br&gt;
The library focused on &lt;code&gt;.tfvars&lt;/code&gt; file refactoring. &lt;br&gt;
It can extract not only terraform variables data, but also code comments from &lt;code&gt;.tfvars&lt;/code&gt; files, which could be very important to keep during the &lt;code&gt;.tfvars&lt;/code&gt; files transformation.&lt;br&gt;
Sometimes it is important to keep original formatting so the library collects information about original maps and lists code style: if they were one-liners, or each property has its own line.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I think the &lt;code&gt;.tfvars&lt;/code&gt; files approach is a good way to manage multi-environment Terraform codebase for huge projects. It allows naturally to implement feature flags and truck based development for Infrastructure as Code.&lt;/p&gt;

&lt;p&gt;The article repository: &lt;a href="https://github.com/musukvl/article-terraform-tfvars-infro" rel="noopener noreferrer"&gt;https://github.com/musukvl/article-terraform-tfvars-infro&lt;/a&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
    </item>
    <item>
      <title>zap log level codes</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Wed, 08 May 2024 13:29:07 +0000</pubDate>
      <link>https://dev.to/musukvl/zap-log-level-codes-cmc</link>
      <guid>https://dev.to/musukvl/zap-log-level-codes-cmc</guid>
      <description>&lt;p&gt;Uber Zap logger level codes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;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;Debug&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Info&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Warn&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DPanic&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Panic&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For details check: &lt;a href="https://github.com/uber-go/zap/blob/cf2f580374cbb923a87d62449ce50d7712b75fa1/level.go#L30" rel="noopener noreferrer"&gt;https://github.com/uber-go/zap/blob/cf2f580374cbb923a87d62449ce50d7712b75fa1/level.go#L30&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>Resource identification in Terraform</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Thu, 29 Jun 2023 14:00:48 +0000</pubDate>
      <link>https://dev.to/musukvl/terraform-resources-identification-5bgg</link>
      <guid>https://dev.to/musukvl/terraform-resources-identification-5bgg</guid>
      <description>&lt;h1&gt;
  
  
  Resource identification in Terraform
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Basic concepts
&lt;/h2&gt;

&lt;p&gt;Each resource created with Terraform present on three levels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resource has definition in .tf-file code as &lt;code&gt;resource&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;Created resource has record in Terraform state file.&lt;/li&gt;
&lt;li&gt;Resource is also actual resource created in a cloud.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On each level resource has its own identification. &lt;br&gt;
You need clearly understand how resource identified on each level to avoid unexpected resource recreation and data loss.&lt;br&gt;
Proper resource identifiers also make your code maintainable and readable.&lt;/p&gt;

&lt;p&gt;The code examples in this article are based on Azure provider. But the same concepts are applicable for other providers.&lt;br&gt;
The link to code examples: &lt;a href="https://github.com/musukvl/article-terraform-resource-identification" rel="noopener noreferrer"&gt;https://github.com/musukvl/article-terraform-resource-identification&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Resource identifier in case of single resource
&lt;/h2&gt;

&lt;p&gt;Let's check the &lt;a href="https://github.com/musukvl/article-terraform-resource-identification/blob/master/001-basic-example/main.tf" rel="noopener noreferrer"&gt;simple example&lt;/a&gt; of azure storage account created with Terraform and track resource identification on each level:&lt;/p&gt;

&lt;p&gt;Resource definition in .tf-file code level is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"application_storage"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It has resource type &lt;code&gt;azurerm_storage_account&lt;/code&gt; and resource name &lt;code&gt;application_storage&lt;/code&gt;. In code the storage account can be referenced as  &lt;code&gt;azurerm_storage_account.application_storage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the terraform state the storage account is identified with &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"resources"&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"managed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application_storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"provider[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;registry.terraform.io/hashicorp/azurerm&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"instances"&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;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/xxxxx/resourceGroups/ary-app-rg/providers/Microsoft.Storage/storageAccounts/aryappsa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aryappsa"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&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;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;Full example of state file is &lt;a href="https://github.com/musukvl/article-terraform-resource-identification/blob/master/001-basic-example/terraform.tfstate.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Actual storage account has &lt;code&gt;/subscriptions/xxxxx/resourceGroups/ary-app-rg/providers/Microsoft.Storage/storageAccounts/aryappsa&lt;/code&gt; id in Azure cloud.   &lt;/p&gt;

&lt;p&gt;Terraform matching resource on each level by corresponding identifier. &lt;/p&gt;

&lt;p&gt;For example, if you change name of the resource in *.tf-file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;  &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"application_storage_NEW"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the &lt;code&gt;terraform plan&lt;/code&gt; operation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform realizes that it has no matching code definition for resource in the state with type &lt;code&gt;azurerm_storage_account&lt;/code&gt; and name &lt;code&gt;application_storage&lt;/code&gt;. So Terraform will destroy resource in Azure and remove it from state.&lt;/li&gt;
&lt;li&gt;Terraform realizes that it has no matching record in the state for resource defined in the code with identifier &lt;code&gt;azurerm_storage_account.application_storage_NEW&lt;/code&gt;. So Terraform will create it in Azure and add record to the state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Identifiers vs attributes
&lt;/h2&gt;

&lt;p&gt;Keep in mind, that &lt;code&gt;name&lt;/code&gt; attribute of &lt;code&gt;azurerm_storage_account&lt;/code&gt; and name of Terraform resource block are different things. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;name&lt;/code&gt; attribute of &lt;code&gt;azurerm_storage_account&lt;/code&gt; is not part of resource identifier. &lt;br&gt;
Changing attributes could cause resource recreation in some cases and depends on resource provider, but changing identifier &lt;em&gt;always&lt;/em&gt; cause resource recreation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Resource identifier in case of &lt;code&gt;for_each&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If &lt;code&gt;for_each&lt;/code&gt; is used in resource definition, multiple instances of resources will be created. Each instance can be addressed by index key.&lt;/p&gt;

&lt;p&gt;For example, let's check the levels for azure storage account resource in &lt;a href="https://github.com/musukvl/article-terraform-resource-identification/blob/master/002-for_each/main.tf" rel="noopener noreferrer"&gt;for_each case example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Resource definition in .tf-file code is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;application&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"document-parser-service"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;storage_account_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arydocparsesvc"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s2"&gt;"email-sender-service"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;storage_account_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aryemailsendersvc"&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"application_storage"&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;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;application&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It has resource type &lt;code&gt;azurerm_storage_account&lt;/code&gt; and resource name &lt;code&gt;application_storage&lt;/code&gt;. In code the storage account can be referenced with index key:  &lt;code&gt;azurerm_storage_account.application_storage["document-parser-service"]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the state we will see two instances of storage account resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;resources:&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"managed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application_storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"provider[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;registry.terraform.io/hashicorp/azurerm&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"instances"&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;"index_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"document-parser-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/xxxxx/resourceGroups/ary-app-rg-document-parser-service/providers/Microsoft.Storage/storageAccounts/arydocparsesvc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arydocparsesvc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&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;span class="nl"&gt;"index_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email-sender-service"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/xxxxx/resourceGroups/ary-app-rg-email-sender-service/providers/Microsoft.Storage/storageAccounts/aryemailsendersvc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aryemailsendersvc"&lt;/span&gt;&lt;span class="w"&gt;            
        &lt;/span&gt;&lt;span class="err"&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;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;Full example of state file is &lt;a href="https://github.com/musukvl/article-terraform-resource-identification/blob/master/002-for_each/terraform.tfstate.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Actual storage accounts have &lt;code&gt;/subscriptions/xxxxx/resourceGroups/ary-app-rg/providers/Microsoft.Storage/storageAccounts/aryappsa&lt;/code&gt; and &lt;code&gt;/subscriptions/xxxxx/resourceGroups/ary-app-rg/providers/Microsoft.Storage/storageAccounts/aryemailsendersvc&lt;/code&gt; ids in Azure cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource identifier in case of &lt;code&gt;count&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;count&lt;/code&gt; statement works pretty similar to &lt;code&gt;for_each&lt;/code&gt; except resource idex is array index number instead of map key string.&lt;/p&gt;

&lt;p&gt;Let's check &lt;a href="https://github.com/musukvl/article-terraform-resource-identification/blob/master/003-count/main.tf" rel="noopener noreferrer"&gt;&lt;code&gt;count&lt;/code&gt; case example&lt;/a&gt; the same as &lt;code&gt;for_each&lt;/code&gt; case example:&lt;/p&gt;

&lt;p&gt;Resource definition in .tf-file code is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;applications&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="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"document-parser-service"&lt;/span&gt;
      &lt;span class="nx"&gt;storage_account_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arydocparsesvc"&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;"email-sender-service"&lt;/span&gt;
      &lt;span class="nx"&gt;storage_account_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aryemailsendersvc"&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"application_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="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;applications&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In code the storage account can be referenced with index key: &lt;code&gt;azurerm_storage_account.application_storage[0]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the state we will see two instances of storage account resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;resources:&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"managed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application_storage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"provider[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;registry.terraform.io/hashicorp/azurerm&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&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;"instances"&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;"index_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/xxxxx/resourceGroups/ary-app-rg-document-parser-service/providers/Microsoft.Storage/storageAccounts/arydocparsesvc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arydocparsesvc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&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;span class="nl"&gt;"index_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/subscriptions/xxxxx/resourceGroups/ary-app-rg-email-sender-service/providers/Microsoft.Storage/storageAccounts/aryemailsendersvc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aryemailsendersvc"&lt;/span&gt;&lt;span class="w"&gt;            
        &lt;/span&gt;&lt;span class="err"&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;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;Full example of state file is &lt;a href="https://github.com/musukvl/article-terraform-resource-identification/blob/master/003-count/terraform.tfstate.json" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Storage accounts has the same IDs and names in azure as in &lt;code&gt;for_each&lt;/code&gt; case example, but the difference identification in state file and in code.&lt;/p&gt;

&lt;p&gt;Using int idex instead of string key is not very convenient, so &lt;code&gt;for_each&lt;/code&gt; is preferred over &lt;code&gt;count&lt;/code&gt; in most cases.&lt;/p&gt;

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

&lt;p&gt;Understanding of resource identification is very important in case of shared terraform modules and for cases when resources can't be easily recreated for refactoring purposes. &lt;/p&gt;

</description>
      <category>terraform</category>
    </item>
    <item>
      <title>Terraform resource dependency graph</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Mon, 03 Apr 2023 05:41:09 +0000</pubDate>
      <link>https://dev.to/musukvl/terraform-resource-dependency-graph-49h0</link>
      <guid>https://dev.to/musukvl/terraform-resource-dependency-graph-49h0</guid>
      <description>&lt;p&gt;This article demonstrates how to manage the creation and updating of Terraform resources by properly configuring their dependencies, using practical examples. &lt;br&gt;
The code examples, available in this &lt;a href="https://github.com/musukvl/article-terraform-graph" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; utilize Azure provider resources, but the concepts discussed apply to any provider.&lt;/p&gt;
&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;p&gt;During planning phase terraform evaluates dependencies between resources and builds a dependency graph. So terraform ensuring the proper order and parallelism for resource change operations.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use resource and module outputs to define dependencies between resources.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;for_each&lt;/code&gt; for collecting outputs from created resources.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;depends_on&lt;/code&gt; with caution, as it can lead to resource recreation due to minor changes in related resources.&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;terraform graph&lt;/code&gt; command to review resource dependencies and refactor them.&lt;/li&gt;
&lt;li&gt;Assign proper resource identifiers wisely to be able using it for lookup.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Using resource output to make dependency
&lt;/h1&gt;

&lt;p&gt;The following example defines Azure resource group and storage account in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&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;"northeurope"&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;"ary-graph-example-rg"&lt;/span&gt;   
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"graph_example_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="nx"&gt;local&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="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"storage_account"&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;"arygrexstacc"&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;local&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="nx"&gt;location&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&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;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="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;(Check code in &lt;a href=""&gt;001-local-for-name&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;terraform plan&lt;/code&gt; command for this example does not return any errors. &lt;br&gt;
But if you run &lt;code&gt;terraform apply&lt;/code&gt; you might see "Resource group 'ary-graph-example-rg' could not be found" error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;│ Error: creating Azure Storage Account "arygrexstacc": storage.AccountsClient#Create: Failure sending request: StatusCode=404 -- Original Error: Code="ResourceGroupNotFound" Message="Resource group 'ary-graph-example-rg' could not be found."    
│
│   with azurerm_storage_account.storage_account,
│   on main.tf line 11, in resource "azurerm_storage_account" "storage_account":
│   11: resource "azurerm_storage_account" "storage_account" {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The apply error happends because terraform creating resource group and storage account in parallel. (Default parallelism for &lt;code&gt;terraform apply&lt;/code&gt; is 10). &lt;br&gt;
Here is a work around for such problem: you can run &lt;code&gt;terraform apply&lt;/code&gt; again and storage account will be created because resource group created before. It would work, but you might have problems with day zero provisioning when you need to recreate everything from scratch.&lt;/p&gt;

&lt;p&gt;The right solution is to define relation between resource group and storage account. &lt;/p&gt;

&lt;p&gt;It is possible to generate depenency graph with &lt;code&gt;terraform graph&lt;/code&gt; command and draw image with Graphviz (DOT):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform graph | dot -Tpng &amp;gt; graph.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph shows, that there is no dependency between resource group and storage account, but they both dependend on &lt;code&gt;local.location&lt;/code&gt; variable. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxlhhc255gx6scri4veeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxlhhc255gx6scri4veeu.png" alt="Resource group and storage account are not dependent" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix is use resource group output &lt;code&gt;azurerm_resource_group.graph_example_rg.name&lt;/code&gt; instead of local variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&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;"northeurope"&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;"ary-graph-example-rg"&lt;/span&gt;   
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"graph_example_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="nx"&gt;local&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="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&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;tags&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"asset-owner"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Mark"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"storage_account"&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;"arygrexstacc"&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;graph_example_rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;- resource output used for dependency&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;local&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;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="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;(Check code in &lt;a href="https://github.com/musukvl/article-terraform-graph/tree/master/002-output-dependency" rel="noopener noreferrer"&gt;002-output-dependency&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Frxnrtr2keq3uhv851cm6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Frxnrtr2keq3uhv851cm6.png" alt="Resource group and storage account are dependent" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the graph shows that resource group and storage account are dependent on each other. &lt;/p&gt;

&lt;h1&gt;
  
  
  Using depends_on to make dependency
&lt;/h1&gt;

&lt;p&gt;Using 'depends_on' is way to enforce dependency between resources. It is not recommended to use it, because it can lead to resource recreation because of minor change in related resource.&lt;/p&gt;

&lt;p&gt;In the following example, resource group tag change, causes recreation of storage account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&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;"northeurope"&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;"ary-graph-example-rg"&lt;/span&gt;   
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"graph_example_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="nx"&gt;local&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="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&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;tags&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"asset-owner"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Mark"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"graph_example_rg"&lt;/span&gt; &lt;span class="p"&gt;{&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_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;graph_example_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="nx"&gt;local&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="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"storage_account"&lt;/span&gt; &lt;span class="p"&gt;{&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;data&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;graph_example_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;"arygrexstacc"&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;data&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;graph_example_rg&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;data&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;graph_example_rg&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;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Check code in &lt;a href="https://github.com/musukvl/article-terraform-graph/tree/master/003-depends_on" rel="noopener noreferrer"&gt;003-depends_on&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For example in case of changing tag value from "Mark" to "Mark1" terraform will generate the following plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt; &lt;span class="c1"&gt;# azurerm_resource_group.graph_example_rg will be updated in-place&lt;/span&gt;
  &lt;span class="err"&gt;~&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"graph_example_rg"&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="s2"&gt;"/subscriptions/5293af6a-eac6-493f-8d6f-e6358448a2ff/resourceGroups/ary-graph-example-rg"&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;"ary-graph-example-rg"&lt;/span&gt;
      &lt;span class="err"&gt;~&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="err"&gt;~&lt;/span&gt; &lt;span class="s2"&gt;"asset-owner"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Mark"&lt;/span&gt; &lt;span class="nx"&gt;-&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Mark1"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;# (1 unchanged attribute hidden)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# azurerm_storage_account.storage_account must be replaced&lt;/span&gt;
&lt;span class="nx"&gt;-&lt;/span&gt;&lt;span class="err"&gt;/+&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"storage_account"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="err"&gt;~&lt;/span&gt; &lt;span class="nx"&gt;access_tier&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Hot"&lt;/span&gt; &lt;span class="nx"&gt;-&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;known&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="err"&gt;~&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;                                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/subscriptions/5293af6a-eac6-493f-8d6f-e6358448a2ff/resourceGroups/ary-graph-example-rg/providers/Microsoft.Storage/storageAccounts/arygrexstacc"&lt;/span&gt; &lt;span class="nx"&gt;-&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;known&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;large_file_share_enabled&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;known&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="err"&gt;~&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;"northeurope"&lt;/span&gt; &lt;span class="c1"&gt;# forces replacement -&amp;gt; (known after apply)&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;"arygrexstacc"&lt;/span&gt;

&lt;span class="nx"&gt;Plan&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;(Check code in &lt;a href="https://github.com/musukvl/article-terraform-graph/tree/master/003-depends_on/update-plan.txt" rel="noopener noreferrer"&gt;003-data-resource/update-plan.txt&lt;/a&gt;)&lt;/p&gt;

&lt;h1&gt;
  
  
  Terraform graph refactoring
&lt;/h1&gt;

&lt;p&gt;Using &lt;code&gt;terraform graph &amp;gt; graph.dot&lt;/code&gt; command is a good way to do dependency refactoring. You can generate graph file before refactoring and after and compare them. No changes in the graph means that you did not break anything.&lt;br&gt;
You can visualize graph with Graphviz (DOT) tool: &lt;code&gt;terraform graph &amp;gt; graph.dot&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The previous example can be refactored to not use &lt;code&gt;depends_on&lt;/code&gt; and having the same dependency graph by using resource output for dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&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;"northeurope"&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;"ary-graph-example-rg"&lt;/span&gt;   
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"graph_example_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="nx"&gt;local&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="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&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;tags&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"asset-owner"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Mark"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"graph_example_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="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;graph_example_rg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="c1"&gt;# &amp;lt;- resource output used for dependency&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"storage_account"&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;"arygrexstacc"&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;data&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;graph_example_rg&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;data&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;graph_example_rg&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;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="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;(Check code in &lt;a href="https://github.com/musukvl/article-terraform-graph/tree/master/004-refactoring" rel="noopener noreferrer"&gt;004-data-resource&lt;/a&gt;)&lt;/p&gt;

&lt;h1&gt;
  
  
  One to may relation example
&lt;/h1&gt;

&lt;p&gt;In the following example, the resource group and storage account defined in the local.config variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&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;"northeurope"&lt;/span&gt;

  &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"sample-rg1"&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;"ary-graph-example-1-rg"&lt;/span&gt;
      &lt;span class="nx"&gt;storage_accounts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"sa11"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arysa11grexstacc"&lt;/span&gt;        
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s2"&gt;"sa12"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arysa12grexstacc"&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="s2"&gt;"sample-rg2"&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;"ary-graph-example-2-rg"&lt;/span&gt;
      &lt;span class="nx"&gt;storage_accounts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"sa21"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arysa21grexstacc"&lt;/span&gt;        
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s2"&gt;"sa22"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arysa22grexstacc"&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;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"graph_example_rg"&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;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&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;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;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;local&lt;/span&gt;&lt;span class="p"&gt;.&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;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;storage_accounts_to_create&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&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;rg_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rg&lt;/span&gt; &lt;span class="nx"&gt;in&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;graph_example_rg&lt;/span&gt; &lt;span class="err"&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;sa_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sa&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;rg_key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;storage_accounts&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"${rg_key}_${sa_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="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;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="nx"&gt;storage_account_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sa&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="p"&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"storage_account"&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;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage_accounts_to_create&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;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;storage_account_name&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;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;resource_group&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;local&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;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="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;(Check code in &lt;a href="https://github.com/musukvl/article-terraform-graph/tree/master/005-one-to-many-relation" rel="noopener noreferrer"&gt;005-one-to-many-relation&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The essential of the example is using local variable to make dependency between created resources and local variable. &lt;br&gt;
So local variable evaluated only when resource groups are created, and then storage accounts are created only when variable evaluated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fncsgrpopkur7sh7fu9ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fncsgrpopkur7sh7fu9ui.png" alt="one-to-many relation" width="636" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is possible to use multiple local variables for different created resources and make join operations between them.&lt;br&gt;
Using local variables for iterating created resources is also a good way to make code more readable and maintainable. &lt;/p&gt;

&lt;p&gt;This example also demonstrates importance of resource identifiers management, in this case local.config map keys used as resource group resource identifiers, and concatenation of resource group key and storage account key used as storage account resource identifiers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azurerm_resource_group.graph_example_rg[&lt;span class="s2"&gt;"sample-rg1"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;: Creating...
azurerm_storage_account.storage_account[&lt;span class="s2"&gt;"sample-rg1_sa11"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;: Still creating... &lt;span class="o"&gt;[&lt;/span&gt;10s elapsed]
azurerm_storage_account.storage_account[&lt;span class="s2"&gt;"sample-rg1_sa12"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;: Still creating... &lt;span class="o"&gt;[&lt;/span&gt;10s elapsed]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the same keys (&lt;code&gt;rg_key&lt;/code&gt;) in the local.config map and as identifier of created resources allows to join created resource with the corresponding configuration in the local.config map.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;rg_key&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rg&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;graph_example_rg&lt;/span&gt; &lt;span class="err"&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;sa_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sa&lt;/span&gt; &lt;span class="nx"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;rg_key&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;storage_accounts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The article provides various examples to illustrate these concepts, such as using resource output to make dependencies, avoiding depends_on when possible, refactoring the Terraform graph, and managing one-to-many relationships between resources. These examples demonstrate the importance of managing resource identifiers and using local variables to make dependencies between created resources, making the code more readable and maintainable.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>Using dotnet tool</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Sun, 18 Dec 2022 14:50:49 +0000</pubDate>
      <link>https://dev.to/musukvl/dotnet-tool-2djd</link>
      <guid>https://dev.to/musukvl/dotnet-tool-2djd</guid>
      <description>&lt;p&gt;Dotnet tools - is a great technology to distribute command line tools.&lt;br&gt;
For example, the following command installs EF Core CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; dotnet-ef
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Listing installed dotnet tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool list &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uninstalling dotnet tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet tool uninstall --global dotnet-ef
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can pack your own command line application as dotnet tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet pack MyCli.csproj --configuration Release --output ./publish`
    -p:PackAsTool=true `
    -p:ToolCommandName=mytool `
    -p:Version=1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It generates &lt;code&gt;MyCli.1.0.0.nupkg&lt;/code&gt; nuget package. &lt;br&gt;
Built tool with be placed into &lt;code&gt;tools\net7.0\any&lt;/code&gt; folder of the archive.&lt;br&gt;
You can push the package into nuget repository as normal nuget package and install the tool from it.&lt;br&gt;
But you can also install the tool from a local folder where nupkg file placed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotnet tool install MyCli --global --add-source ./publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;dotnet tool&lt;/code&gt; is a part of .Net Core SDK, so you can't install your tool the environment where no dotnet SDK installed. But you can install dotnet tool to environment with SDK installed and copy it from there. Check the Andey Lock example for &lt;a href="https://andrewlock.net/running-net-core-global-tools-in-non-sdk-docker-images/" rel="noopener noreferrer"&gt;copying dotnet tool binaries to non-sdk docker image&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>html</category>
      <category>learning</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Access to GO modules in private Azure DevOps repositories</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Fri, 09 Dec 2022 08:39:47 +0000</pubDate>
      <link>https://dev.to/musukvl/access-to-go-modules-in-private-azure-devops-repositories-4p1h</link>
      <guid>https://dev.to/musukvl/access-to-go-modules-in-private-azure-devops-repositories-4p1h</guid>
      <description>&lt;p&gt;If you tried using $(System.AccessToken) to authorize git downloading go module from you private Azure DevOps git repository, you could face with the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fatal: unable to connect to dev.azure.com:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Official Microsoft documentation asks you to create PAT token for access git repoisitories: &lt;br&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/devops/repos/git/go-get?view=azure-devops#https" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/devops/repos/git/go-get?view=azure-devops#https&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But you can use $(System.AccessToken) of pipeline identity instead. So you can avoid storing PAT token and renew it on expiration.&lt;/p&gt;

&lt;p&gt;Go to Project Settings =&amp;gt; Pipelines =&amp;gt; Settings and make sure you have "Protect access to repositories in YAML pipelines" checkbox disabled. It will allow &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/access-tokens?view=azure-devops&amp;amp;tabs=yaml#scoped-build-identities" rel="noopener noreferrer"&gt;pipeline identities&lt;/a&gt; access other git repositories:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flis9alll1r7vx3lsqoa4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flis9alll1r7vx3lsqoa4.png" alt=" " width="644" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Be sure *&lt;em&gt;GOPRIVATE *&lt;/em&gt; environment variable set to dev.azure.com&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOPRIVATE=dev.azure.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you added it you can execute the following commaind in your pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; url.&lt;span class="s2"&gt;"https://test:&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;System.AccessToken&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;@dev.azure.com/&amp;lt;organization&amp;gt;/&amp;lt;project&amp;gt;/_git/&amp;lt;repo&amp;gt;"&lt;/span&gt;.insteadOf &lt;span class="s2"&gt;"https://dev.azure.com/&amp;lt;organization&amp;gt;/&amp;lt;project&amp;gt;/&amp;lt;repo&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Terraform basics</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Sun, 27 Nov 2022 15:07:02 +0000</pubDate>
      <link>https://dev.to/musukvl/terraform-basics-2dkg</link>
      <guid>https://dev.to/musukvl/terraform-basics-2dkg</guid>
      <description>&lt;p&gt;The following article describes the basic terminology and concepts of Terraform and can be used as a first step in working with Terraform.&lt;/p&gt;

&lt;p&gt;Terraform is a tool to manage your infrastructure. Basicaly it calls vendors API to create infrastructure resources.&lt;/p&gt;

&lt;h1&gt;
  
  
  Resources
&lt;/h1&gt;

&lt;p&gt;Terraform resource is anything that can be managed via Terraform:) For example, it could be Azure resource group or AWS S3 bucket or GitHub repository, or anything else what supports terraform.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Feayegd9riawof4rntz1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Feayegd9riawof4rntz1y.png" alt="Terraform resources" width="800" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Provider
&lt;/h1&gt;

&lt;p&gt;For creating Azure resources like Azure Key Vault or Azure SQL Database, Terraform needs to call Azure API. Terraform uses &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs" rel="noopener noreferrer"&gt;Azure provider&lt;/a&gt; for this purpose. Terraform provider is a plugin for Terraform. The provider incapsulates API Client for vendor API.  You can use multipe providers in your project at the same time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fd6dr1u6ouh4cptvjj6nk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd6dr1u6ouh4cptvjj6nk.png" alt="Terraform provider diagram" width="800" height="93"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Providers are supplied by Hashicorp, API owners, or enthusiasts. You can find list of publicly available providers in &lt;a href="https://registry.terraform.io/browse/providers" rel="noopener noreferrer"&gt;Hashicorp Provider Registry&lt;/a&gt;.  &lt;/p&gt;

&lt;h1&gt;
  
  
  HCL
&lt;/h1&gt;

&lt;p&gt;You need to use Hashicorp configuration language (HCL) to declare resources you want to have. Instead of describing operations to create resource, you describe resources in declarative manner. &lt;br&gt;
Terraform project is a set of .tf files. You can use Visual Studio Code or Jetbrains IDEA to work on it.&lt;br&gt;
The following example declares Azure Resource Group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"resource_group"&lt;/span&gt; &lt;span class="p"&gt;{&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;"westeurope"&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;"demo-rg"&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="s2"&gt;"asset-owner"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Alexey Ryazhskikh"&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;h1&gt;
  
  
  Terraform state
&lt;/h1&gt;

&lt;p&gt;Terraform state is a database storing the information of existing resources. As soon as Terraform creates a resource, it writes information into the state database.&lt;br&gt;
Then Terraform compares HCL declaration of resources and information about resources stored in the state. &lt;br&gt;
Terraform keeps the state and the declaration synchronized. It creates missing resources, deletes resources if they were removed from declaration, etc.&lt;br&gt;
The following example shows the state for the resource group described above. As you see, it stores not only resource id in Azure but also properties like &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;tags&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Frlhmg6302shx6o8uz1h0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Frlhmg6302shx6o8uz1h0.png" alt="Terraform state for resource group" width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can trigger changes with Terraform CLI.&lt;/p&gt;
&lt;h1&gt;
  
  
  Terraform &lt;code&gt;plan&lt;/code&gt; command
&lt;/h1&gt;

&lt;p&gt;Terraform &lt;code&gt;plan&lt;/code&gt; command calculates which resources needs to be created, updated or deleted. The &lt;code&gt;plan&lt;/code&gt; command generates tfplan file describing all needed modifications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fiailnyio0ffey8bcoxdj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fiailnyio0ffey8bcoxdj.png" alt="Terraform Plan: plan(hcl, state) = plan" width="800" height="248"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Terraform &lt;code&gt;apply&lt;/code&gt; command
&lt;/h1&gt;

&lt;p&gt;Terraform &lt;code&gt;apply&lt;/code&gt; command performs planned changes via Terraform providers and updates terraform state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F7ft0y58tq0g270p4ayqi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F7ft0y58tq0g270p4ayqi.png" alt=" " width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Terraform module
&lt;/h1&gt;

&lt;p&gt;Terraform module is the way how you can declare multiple resources at once with some parameters. So you can think about the module as a function that declares a group of resources.&lt;br&gt;
The following example shows a module usage example that creates an azure resource group and role assignments under the hood:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"resource_group"&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;"app.terraform.io/my-corp/resource_group/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;"1.0.0"&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;var&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;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-demo-rg"&lt;/span&gt;
  &lt;span class="nx"&gt;role_assignments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"contributors"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;role_definition_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Contributor"&lt;/span&gt;
      &lt;span class="nx"&gt;principal_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"d3a2437e-15f1-4495-ba65-e10bf91578f2"&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;Modules allow the creation of abstraction layers in your terraform code. For example, you can create a database module to add Azure SQL database. After some time, you might need to implement backup logic for all databases you declared. You can extend your database module with storage account declaration, and all databases declared with your module will get the storage account after updating to the new version of the module.&lt;br&gt;
Modules can be declared localy for each terraform project, or can be published to private or public module registry: &lt;a href="https://registry.terraform.io/browse/modules" rel="noopener noreferrer"&gt;https://registry.terraform.io/browse/modules&lt;/a&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
    </item>
    <item>
      <title>The Terraform downsides you should know</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Sun, 16 Oct 2022 19:20:55 +0000</pubDate>
      <link>https://dev.to/musukvl/the-terraform-downsides-you-should-know-c8k</link>
      <guid>https://dev.to/musukvl/the-terraform-downsides-you-should-know-c8k</guid>
      <description>&lt;p&gt;Terraform is an excellent tool for infrastructure provisioning, you can find a lot of pros to starting using Terraform, but you also should know about the downside of Terraform.&lt;/p&gt;

&lt;h1&gt;
  
  
  Providers limitation
&lt;/h1&gt;

&lt;p&gt;Terraform essentially is the way to create resources via providers. Under the hood provider calls some 3d party service API (for example, AWS API or Azure API). &lt;br&gt;
So to use a new version of API, you need to have a new version of the Terraform provider. In reality, Terraform providers might not support the latest features of API. Or even some old features available via API might not be supported by the Terraform provider. &lt;br&gt;
For example, Microsoft Azure DevOps API supports kubernetes resources creation, but it is not supported by terraform provider. The &lt;a href="https://github.com/microsoft/terraform-provider-azuredevops/issues/584" rel="noopener noreferrer"&gt;Pull request&lt;/a&gt; with this feature has not being approved for a long time.&lt;br&gt;
Some companies develop Terraform providers for their API and maintain it. But some Terraform providers are created by enthusiasts who might abandon their project anytime. &lt;br&gt;
Hashicorp knows about this problem, so they try to support many providers by themself until official providers are provided. &lt;br&gt;
&lt;a href="https://registry.terraform.io/search/providers" rel="noopener noreferrer"&gt;https://registry.terraform.io/search/providers&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.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%2Fzrg0kf3x1pnv2t5y5rxt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzrg0kf3x1pnv2t5y5rxt.png" alt="Azure DevOps Providers" width="800" height="268"&gt;&lt;/a&gt;&lt;br&gt;
I recommend to check provider git history and activity before using terraform provider.&lt;/p&gt;

&lt;h1&gt;
  
  
  HCL limitations
&lt;/h1&gt;

&lt;p&gt;HCL is Terraform language, it is a very limited. &lt;br&gt;
For example, you need to use hacks to emulate "if" statements, like following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3iagyaq6v3xhdk2krro7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3iagyaq6v3xhdk2krro7.png" alt="count as if" width="426" height="99"&gt;&lt;/a&gt;&lt;br&gt;
Because of &lt;code&gt;count&lt;/code&gt; usage terraform creates list of resources instead of single resource. So the path for created vm is: &lt;code&gt;module.test_vm[0]&lt;/code&gt; just because we want to have conditional creation. &lt;a href="https://stackoverflow.com/questions/60231309/terraform-conditional-creation-of-a-resource-based-on-a-variable-in-tfvars#answer-60231673" rel="noopener noreferrer"&gt;StackOverflow question about it.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But Hashicorp improves HCL constantly, for example we just got Terraform 1.3 with &lt;a href="https://www.hashicorp.com/blog/terraform-1-3-improves-extensibility-and-maintainability-of-terraform-modules#optional-object-type-attributes-with-defaults" rel="noopener noreferrer"&gt;optional object fields supported&lt;/a&gt;, so now we can have defaul values no only for entire variable, but also for object field.&lt;/p&gt;

&lt;p&gt;HCL support in IDEs still far from perfect. Autocomplete is not very smart and reminds me early years of JavaScript. Jetbrains did a great job supporting HCL in their IDEA familiy. &lt;br&gt;
Autocomplete doesn't support object definition in maps. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fprxkxba6w3wq26jwpjzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fprxkxba6w3wq26jwpjzj.png" alt="no suggestions" width="220" height="115"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Apply-only errors
&lt;/h1&gt;

&lt;p&gt;Even if Terraform plan is green, Terraform apply might be errored.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbn3fh17k1fd5b8ybg0i1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbn3fh17k1fd5b8ybg0i1.png" alt="Green plan, red apply" width="426" height="310"&gt;&lt;/a&gt;&lt;br&gt;
Terraform apply does real API calls, which might fail because of validation errors, eg. "resource name is too long" or network connectivity issues. For example, you might ask to create a database before the database server is created. Terraform requires you manually define resource dependencies with the &lt;code&gt;depends_on&lt;/code&gt; property of the resource.&lt;br&gt;&lt;br&gt;
It would help if you had a stage environment with the same terraform state to test Terraform apply before doing the same change for production environment.&lt;/p&gt;

&lt;h1&gt;
  
  
  State is fragile
&lt;/h1&gt;

&lt;p&gt;Terraform can manage resource only if it is tracked by Terraform state. So if a resource was deleted manually, you need manually delete the information about the resource from Terraform state. Terraform state might "drift" for various reasons, and fixing it might be tricky.&lt;br&gt;
For example, I wrote script to get resources ids from REST API, and import it to terraform with &lt;code&gt;terraform import&lt;/code&gt; command.&lt;br&gt;
The drift detection is one of features Terraform Cloud, but not terraform itself.&lt;/p&gt;

&lt;h1&gt;
  
  
  Terraform Cloud
&lt;/h1&gt;

&lt;p&gt;Terraform cloud has numer of cool features you might like to have. But it is also vendor lock and third-party dependency you can't control or diagnose. &lt;br&gt;
The most irritating thing is "You have exceeded the API's rate limit." error in the middle of work you need to do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fzajkqjk7bvx9ofofnhmk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzajkqjk7bvx9ofofnhmk.png" alt="Terraform Cloud error" width="317" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Despite on all this issues, I think that Terraform is a great tool for infrastructure manament. &lt;/p&gt;

</description>
      <category>terraform</category>
    </item>
    <item>
      <title>Docker concepts you should know</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Fri, 11 Feb 2022 12:21:35 +0000</pubDate>
      <link>https://dev.to/musukvl/docker-concepts-you-should-know-2jo8</link>
      <guid>https://dev.to/musukvl/docker-concepts-you-should-know-2jo8</guid>
      <description>&lt;h1&gt;
  
  
  Difference between image and container
&lt;/h1&gt;

&lt;p&gt;You need to feel difference between docker "image" and "container".&lt;br&gt;
Make sure you know understand the flow:&lt;br&gt;
You have Dockerfile to build image you need to run containers. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://mermaid.live/edit#pako:eNpNjT0LAjEQRP9K2MLqrtAyhY1aCFbaplmTzV0wH7JuEDnuvxsORbt5Mw9mAlscgQYfy9OOyKJOZ5P3xd6IfYik-l65hdS1hugab9Ux4fC_cM1LvytZMGTitVr9YAMdJOKEwbWjyWSlDMhIiQzoFh15rFEMmDw3td4dCh1ckMKgPcYHdYBVyuWVLWjhSl9pH3BgTB9rfgNciEU6" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmermaid.ink%2Fimg%2Fpako%3AeNpNjT0LAjEQRP9K2MLqrtAyhY1aCFbaplmTzV0wH7JuEDnuvxsORbt5Mw9mAlscgQYfy9OOyKJOZ5P3xd6IfYik-l65hdS1hugab9Ux4fC_cM1LvytZMGTitVr9YAMdJOKEwbWjyWSlDMhIiQzoFh15rFEMmDw3td4dCh1ckMKgPcYHdYBVyuWVLWjhSl9pH3BgTB9rfgNciEU6" alt="docker flow" width="652" height="174"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Docker file build contexts
&lt;/h1&gt;

&lt;p&gt;You need to understand the context of each line of dockerfile&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what is the current build context (directory on agent)&lt;/li&gt;
&lt;li&gt;what is the current working directory on image&lt;/li&gt;
&lt;li&gt;what is the current user &lt;/li&gt;
&lt;li&gt;what is the current stage (for multistage build)&lt;/li&gt;
&lt;li&gt;how it affects layer cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent is a machine where &lt;code&gt;docker build&lt;/code&gt; command started. &lt;code&gt;docker build&lt;/code&gt; command has required parameter "build context" which is folder on agent machine. &lt;br&gt;
For example, the &lt;code&gt;COPY &amp;lt;src&amp;gt; &amp;lt;dest&amp;gt;&lt;/code&gt; command &lt;code&gt;&amp;lt;src&amp;gt;&lt;/code&gt; parameter is path relative to the current build context folder. &lt;br&gt;
&lt;code&gt;&amp;lt;dest&amp;gt;&lt;/code&gt; parameter is path relative to the current working directory on image file system.&lt;/p&gt;

&lt;p&gt;For example this &lt;a href="https://github.com/musukvl/docker-concepts-you-should-know/blob/master/src/webapi/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile &lt;/a&gt; has the following file systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WORKDIR /src
COPY . .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tr&gt;
     &lt;td&gt;Build context (agent)&lt;/td&gt;
     &lt;td&gt;Work directory (image)&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
     &lt;td&gt;
       &lt;pre&gt;
- docker-concepts-you-should-know
  - src
    - WebApi
      - WebApi.csproj
      - Program.cs
      - Dockerfile
       &lt;/pre&gt;
     &lt;/td&gt;
     &lt;td&gt;
       &lt;pre&gt;
- src
  - src
    - WebApi
      - WebApi.csproj
      - Program.cs
      - Dockerfile
       &lt;/pre&gt;
     &lt;/td&gt;
  &lt;/tr&gt;   
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Build time and Runtime
&lt;/h1&gt;

&lt;p&gt;You are able to run apps while &lt;code&gt;docker build&lt;/code&gt; building image with &lt;code&gt;RUN&lt;/code&gt; statement. And you can run your apps in running container with &lt;code&gt;ENTRYPOINT&lt;/code&gt; or with &lt;code&gt;docker exec&lt;/code&gt;. Normally you might need to run your development tools build time, but your service or utils (like database migration) in runtime.&lt;br&gt;
Build time executions are focused on image content, for example you might want to build binaries from source code. Build time you don't have your private network and volumes, so you can't have access to database. &lt;/p&gt;
&lt;h1&gt;
  
  
  Layout caching
&lt;/h1&gt;

&lt;p&gt;Each statement in docker file produces image cache layer on docker host machine. &lt;br&gt;
Cache layers could help application build time, for example &lt;br&gt;
&lt;code&gt;dotnet restore&lt;/code&gt; result will be cached if files copied to the docker image are the same. That is why default &lt;a href="https://github.com/musukvl/docker-concepts-you-should-know/blob/master/src/webapi/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile &lt;/a&gt; generated by visual studio copies csproj files first, and other files only after dotnet restore.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WORKDIR /src
COPY ["src/webapi/webapi.csproj", "src/webapi/"]
RUN dotnet restore "src/webapi/webapi.csproj"
COPY . .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Andrew Lock wrote excellent article about this optimization.&lt;br&gt;
&lt;a href="https://andrewlock.net/optimising-asp-net-core-apps-in-docker-avoiding-manually-copying-csproj-files/" rel="noopener noreferrer"&gt;https://andrewlock.net/optimising-asp-net-core-apps-in-docker-avoiding-manually-copying-csproj-files/&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker-compose
&lt;/h1&gt;

&lt;p&gt;Docker compose calls Docker under the hood. But docker-compose has it's own behavior and some commands could work differently.&lt;/p&gt;

&lt;h1&gt;
  
  
  Entrypoint
&lt;/h1&gt;

&lt;p&gt;You can use shell script as Entrypoint. If you have windows developer environment you need to remember:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set lr for .sh files in .gitattributes
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*.sh text eol=lf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Set +x for .sh file in git metadata
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git update-index --chmod=+x foo.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or you can use &lt;a href="https://tortoisegit.org/" rel="noopener noreferrer"&gt;TortoiseGit&lt;/a&gt;:&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Link between Azure VM Scale Set and Azure DevOps Agent Pool</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Thu, 30 Dec 2021 09:11:23 +0000</pubDate>
      <link>https://dev.to/musukvl/azure-vm-scale-set-for-azure-devops-agent-pool-10le</link>
      <guid>https://dev.to/musukvl/azure-vm-scale-set-for-azure-devops-agent-pool-10le</guid>
      <description>&lt;p&gt;This article describes link between Azure VM Scale Set and Azure DevOps agent pool.&lt;/p&gt;

&lt;p&gt;Azure DevOps allows to create Agent Pool based on Azure VM Scale Set. With this setup Azure DevOps Agent Pool manages Azure ScaleSet to scale up or down number of agents depend on how many agents required for pipelines in particular moment. &lt;br&gt;
&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jitzcpk8jfrv03nd1l44.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2Fjitzcpk8jfrv03nd1l44.png" alt="Azure Agent Pool and VM Scale Set interaction" width="800" height="354"&gt;&lt;/a&gt;&lt;br&gt;
The scale set creates new instances of virtual machines with Azure Devops agent installed on it. Agent is able to interact with the pool after it is created.&lt;/p&gt;

&lt;p&gt;There are two actions happen in time of Pool creation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure DevOps adds tag __AzureDevOpsElasticPool="pool-name" to Azure Scale Set to identify scale set linked to it.&lt;/li&gt;
&lt;li&gt;Azure DevOps adds Azure Pipelines Agent extension to virtual machine scale set and configures it with required PAT token. This extension is being called by scale set to install agent software to just created virtual machine. (Check &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/scale-set-agents?view=azure-devops#lifecycle-of-a-scale-set-agent" rel="noopener noreferrer"&gt;Lifecycle of a Scale Set Agent&lt;/a&gt; manual)
&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/13bj572dgf0f4y98bhpd.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2F13bj572dgf0f4y98bhpd.png" alt="VM Scale Set extensions" width="800" height="297"&gt;&lt;/a&gt;
If you recreate vm scale set without __AzureDevOpsElasticPool tag or delete this tag you will see the following error in Diagnostics tab of your pool in Azure Devops UI: 
&lt;code&gt;The Resource 'Microsoft.Compute/virtualMachineScaleSets/test-vmss' under resource group 'test-vmss-rg' was not found. For more details please go to https://aka.ms/ARMResourceNotFoundFix
&lt;/code&gt;
&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2cjdbnqeywq50jiavnw.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.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%2Fh2cjdbnqeywq50jiavnw.png" alt="Pool Error" width="800" height="197"&gt;&lt;/a&gt;
If you recreate VM scale set with __AzureDevOpsElasticPool tag, but without the Azure Pipelines Agent extension, you will have no agents connected to the Pool, bu Azure DevOps will upscale the number of VM instances.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only way I know to add the Agent extension to scale set is to recreate Pool. &lt;br&gt;
But here is an issue: you can't create pool with existing scale set if this scale set already has __AzureDevOpsElasticPool  tag on it.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>azuredevops</category>
    </item>
    <item>
      <title>Azure DevOps pipeline optimization for .net core projects</title>
      <dc:creator>Alexey Ryazhskikh </dc:creator>
      <pubDate>Tue, 04 May 2021 21:36:17 +0000</pubDate>
      <link>https://dev.to/musukvl/azure-devops-pipeline-optimization-for-net-core-26m7</link>
      <guid>https://dev.to/musukvl/azure-devops-pipeline-optimization-for-net-core-26m7</guid>
      <description>&lt;p&gt;I own a pipeline that builds, tests, and deploys many asp.net core services to k8s cluster. The pipeline is running on Azure DevOps hosted agents. They are scalable, but not robust, so I need to do some performance optimizations for my pipeline.&lt;/p&gt;

&lt;p&gt;The pipeline has three main stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;running various automated tests&lt;/li&gt;
&lt;li&gt;building and pushing number of docker images for k8s deployment&lt;/li&gt;
&lt;li&gt;deploying services by terraform&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running stages in parallel
&lt;/h2&gt;

&lt;p&gt;My pipeline runs in two modes: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pull request validation&lt;/strong&gt; - it runs all tests and executes only &lt;code&gt;terraform plan&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment mode&lt;/strong&gt; - it runs all tests and does actual deployment to the k8s cluster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;My pipeline doesn't change anything in PR validation mode, so I can validate terraform configuration, Dockerfiles, and run autotests in parallel.&lt;/p&gt;

&lt;p&gt;Normal flow:&lt;br&gt;
&lt;a href="https://media2.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%2F1fn29rwnz02nambimvfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1fn29rwnz02nambimvfw.png" alt="Normal stages flow" width="781" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pull Request flow:&lt;br&gt;
&lt;a href="https://media2.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%2F65xegxsfss87426u4a4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F65xegxsfss87426u4a4w.png" alt="Pull Request flow" width="274" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default stages are depend on each other, but you can change this behaviour by forcing &lt;code&gt;dependsOn: []&lt;/code&gt; in azure-pipelines.yml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;isPullRequestValidation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ eq(variables['Build.Reason'], 'PullRequest') }}&lt;/span&gt;

&lt;span class="nn"&gt;...&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy_to_dev&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dev"&lt;/span&gt;
  &lt;span class="s"&gt;${{ if eq(variables.isPullRequestValidation, &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&gt;) }}&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SQL Server startup optimization
&lt;/h2&gt;

&lt;p&gt;For running integration tests, I use docker-compose with SQL Server and test container declarations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.7"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;mssql&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/mssql/server"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;SA_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sa123321"&lt;/span&gt;
      &lt;span class="na"&gt;ACCEPT_EULA&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Y"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;5433:1433&lt;/span&gt;

  &lt;span class="na"&gt;integrationTests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IntegrationTests&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my/my_integration_tests&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;..&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./Integration.Tests/Dockerfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a problem with running SqlServer in docker - it needs few minutes to start.&lt;br&gt;
My idea is to run SQL Server before start building test containers. &lt;br&gt;
For that, I split the docker-compose.yml into two ones: &lt;code&gt;docker-compose.db.yml&lt;/code&gt; and &lt;code&gt;docker-compose.tests.yml&lt;/code&gt;.&lt;br&gt;
Docker-compose services running with the same &lt;code&gt;--project-name&lt;/code&gt; share the same scope.&lt;br&gt;
So my integration test job does the following steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.db.yml    &lt;span class="nt"&gt;--project-name&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.tests.yml &lt;span class="nt"&gt;--project-name&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;build
docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.tests.yml &lt;span class="nt"&gt;--project-name&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;up &lt;span class="nt"&gt;-d&lt;/span&gt;
docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.tests.yml &lt;span class="nt"&gt;--project-name&lt;/span&gt; &lt;span class="nb"&gt;test exec &lt;/span&gt;integrationTests &lt;span class="s2"&gt;"/src/Integration.Tests/init-db.sh"&lt;/span&gt;
docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.tests.yml &lt;span class="nt"&gt;--project-name&lt;/span&gt; &lt;span class="nb"&gt;test exec &lt;/span&gt;integrationTests dotnet &lt;span class="nb"&gt;test&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Fz68vh9muvs0cc92aixpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fz68vh9muvs0cc92aixpf.png" alt="Integration test tasks" width="334" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the optimization &lt;code&gt;Init test database&lt;/code&gt; step doesn't wait for SQL Server running, and I found no build speed degradation.&lt;/p&gt;

&lt;h2&gt;
  
  
  .Net Core services build optimization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build Parallelism
&lt;/h3&gt;

&lt;p&gt;Azure DevOps agents do not guarantee persistence for docker cache between jobs running. However, you can be lucky and get an agent with prepopulated docker cache.&lt;br&gt;
Anyway, you surely can rely on the docker cache for layers built due to the current job. &lt;br&gt;
It might be better to have many build tasks in one job, rather than having multiple parallel jobs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Dockerfile code generation
&lt;/h3&gt;

&lt;p&gt;My pipeline builds one .Net solution with C# project for each service. Each service has a similar Dockerfile. &lt;br&gt;
I created a simple template tool for generating Dockerfiles for each service to keep them identical as much as possible.&lt;/p&gt;
&lt;h3&gt;
  
  
  Copy and restore .csproj
&lt;/h3&gt;

&lt;p&gt;There is a recommendation to copy only *.csproj files for all projects and restore them before copying other source files. It allows creating a long-live caching image layer with restored dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# copy only *.csproj and restore&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ["MyService/MyService.csproj", "MyService/"]&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ["My.WebAPI/My.WebAPI.csproj", "My.WebAPI/"]&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore &lt;span class="s2"&gt;"MyService/MyService.csproj"&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore &lt;span class="s2"&gt;"My.WebAPI/My.WebAPI.csproj"&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build My.WebAPI/My.WebAPI.csproj&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not so important for Azure DevOps hosted agents, but useful for the local builds. &lt;br&gt;
But you need to maintain a list of *.csproj in each Dockerfile of your project. So every new .csproj leads you to update every Dockerfile in your solution. &lt;br&gt;
I think code-generation is the best way to automate it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Do more, but once
&lt;/h3&gt;

&lt;p&gt;Building single *.csproj with all dependencies takes about 1 minute for my case. Building an entire solution takes ~3 minutes. But thanks to image layer caching I need to build the solution only once. &lt;br&gt;
Here is an example of a service Dockerfile I have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:5.0-buster-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;

&lt;span class="c"&gt;# creating common references cache&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ["Common/Common.csproj", "Common/"]&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore &lt;span class="s2"&gt;"Common/Common.csproj"&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# exclude test projects from restore/build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet sln ./My.sln remove &lt;span class="k"&gt;**&lt;/span&gt;/&lt;span class="k"&gt;*&lt;/span&gt;Tests.csproj
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore ./My.sln

&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet build ./My.sln &lt;span class="nt"&gt;-c&lt;/span&gt; Release

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; "Services/My.WebAPI"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;publish&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish &lt;span class="s2"&gt;"My.WebAPI.csproj"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; Release &lt;span class="nt"&gt;-o&lt;/span&gt; /app/publish

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=publish /app/publish .&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "My.WebAPI.dll"]&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;There is another way to improve solution build speed: exclude unnecessary projects from &lt;code&gt;.sln&lt;/code&gt; file with &lt;code&gt;dotnet sln&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;A piece of job execution that builds the services&lt;br&gt;
&lt;a href="https://media2.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%2Fxvso3bcsfbb7v6vx3um7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxvso3bcsfbb7v6vx3um7.png" alt="Services images build and publish" width="341" height="332"&gt;&lt;/a&gt;&lt;br&gt;
As you see, building the entire solution took 2 minutes for the first service, other services used created cache and do only &lt;code&gt;RUN dotnet publish&lt;/code&gt; for 30 seconds. &lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Job parallelism and utilizing docker layer cache are orthogonal technics, but you can find the balance for your project.&lt;/li&gt;
&lt;li&gt;Docker-compose for integration tests could be split: so one set of services could be built while the other set is initializing.&lt;/li&gt;
&lt;li&gt;Code generation can help to keep Dockerfiles similar and utilize the docker layer cache.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aspnetcore</category>
      <category>devops</category>
      <category>azuredevops</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
