<?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>Forem: Robert Nemet</title>
    <description>The latest articles on Forem by Robert Nemet (@madmaxx).</description>
    <link>https://forem.com/madmaxx</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%2F602199%2F40fa44e3-0a1c-42ce-9db1-34d8a83d4b4d.png</url>
      <title>Forem: Robert Nemet</title>
      <link>https://forem.com/madmaxx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/madmaxx"/>
    <language>en</language>
    <item>
      <title>Automating Pull Request Reviews With CodiumAI PR-Agent</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Mon, 11 Dec 2023 17:04:09 +0000</pubDate>
      <link>https://forem.com/madmaxx/automating-pull-request-reviews-with-codiumai-pr-agent-4h6h</link>
      <guid>https://forem.com/madmaxx/automating-pull-request-reviews-with-codiumai-pr-agent-4h6h</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;If you are a developer, you know how much time you spend on pull request reviews. We all try to write the best code we can. When we are done, we ask our team members to review our code. Sometimes, making a pull request is more complex than it sounds. Over time, teams learn that pull requests need to be standardized to speed things up and improve communication. That is why we have a pull request template, for example. Sometimes, even that is not enough. Because the speed at which the code is sent out is increasing. More pull requests are created. And the number of pull requests that need to be reviewed is expanding. Ah...&lt;/p&gt;

&lt;p&gt;Question: In the era of AI, can we automate pull request reviews? At least it can dissect a pull request and give us some insights.&lt;/p&gt;

&lt;p&gt;Let's do this with some small pull requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking for the right tool
&lt;/h2&gt;

&lt;p&gt;I need a tool or AI assistant that can do this for me. Obviously, as I have my code on GitHub, I need a tool that can integrate with GitHub. So, the first choice was GitHub's &lt;a href="https://githubnext.com/projects/copilot-for-pull-requests/"&gt;Copilot for pull requests&lt;/a&gt;. And it is not free. It is still in beta, and you need to sign into &lt;a href="https://githubnext.com/projects/copilot-for-pull-requests/"&gt;the waitlist&lt;/a&gt; to access it. I'm not so patient, so I searched further.&lt;/p&gt;

&lt;p&gt;Next, I found a solution &lt;a href="https://www.codium.ai/products/git-plugin/"&gt;CodiumAI PR-Agent&lt;/a&gt;. It is free and open-source. Just from its description, it looks like something that can fit my needs. I decided to give it a try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation &amp;amp; usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;Since I have my remote repository on GitHub to enable CodiumAI PR-Agent to access my code and do its magic, I need to create a GitHub token. Also, CodiumAI PR-Agent uses OpenAI API, which means I must also have an OpenAI API key. So, before you start, make sure you have both of them.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to create a GitHub token
&lt;/h4&gt;

&lt;p&gt;I hope you already have a GitHub account. If not, create one. Then follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your GitHub account settings&lt;/li&gt;
&lt;li&gt;Click on Developer settings&lt;/li&gt;
&lt;li&gt;Click on Personal access tokens&lt;/li&gt;
&lt;li&gt;Click on Generate new token&lt;/li&gt;
&lt;li&gt;Give your token a name and select scopes&lt;/li&gt;
&lt;li&gt;Click on Generate token&lt;/li&gt;
&lt;li&gt;Copy your token and save it somewhere safe&lt;/li&gt;
&lt;li&gt;You will need it later&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Or you can follow this &lt;a href="https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token"&gt;link&lt;/a&gt; for more detailed instructions.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to create OpenAI API key
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://beta.openai.com/"&gt;OpenAI&lt;/a&gt; and create an account&lt;/li&gt;
&lt;li&gt;Go to your account settings&lt;/li&gt;
&lt;li&gt;Click on API keys&lt;/li&gt;
&lt;li&gt;Click on Create a new API key&lt;/li&gt;
&lt;li&gt;Copy your API key and save it somewhere safe&lt;/li&gt;
&lt;li&gt;You will need it later&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Or go to this &lt;a href="https://platform.openai.com/api-keys"&gt;link&lt;/a&gt; to add an API key.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to use Codium PR-Agent from CLI
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Setup
&lt;/h4&gt;

&lt;p&gt;There are two ways to do this: Docker or a Python virtual environment. I'll be using Docker. So, to start using Codium PR-Agent from CLI, first set the above-received API keys as environment variables. You can do this by running these commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your_github_token&amp;gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your_openai_api_key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a pull request on your GitHub repository. Copy the URL of your pull request and export it as an environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PR_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your_pr_url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can run Codium PR-Agent from CLI by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; OPENAI.KEY&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; GITHUB.USER_TOKEN&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; codiumai/pr-agent:latest &lt;span class="nt"&gt;--pr_url&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PR_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even better would be making an alias for this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;pragent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'docker run --rm -it -e OPENAI.KEY=${OPENAI_API_KEY} -e GITHUB.USER_TOKEN=${GITHUB_TOKEN} codiumai/pr-agent:latest --pr_url ${PR_URL}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run Codium PR-Agent from CLI by running this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pragent &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Usage
&lt;/h4&gt;

&lt;p&gt;Codium PR-Agent has several commands. Let's start with ones related to pull requests.&lt;/p&gt;

&lt;h5&gt;
  
  
  Generate Pull Request Description
&lt;/h5&gt;

&lt;p&gt;Let's first start with generating a pull request description. To do this, run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pragent describe &lt;span class="nt"&gt;--keep_original_user_title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="nt"&gt;--add_original_user_description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This command will generate a description for your pull request and add it to your pull request. I want to keep your original description and title.&lt;br&gt;
I added &lt;code&gt;--keep_original_user_title=true&lt;/code&gt; and &lt;code&gt;--add_original_user_description=true&lt;/code&gt; flags. &lt;br&gt;
You can omit these flags if you don't want to keep your original title and description.&lt;/p&gt;

&lt;p&gt;And the results are surprisingly good but also a bit disappointing. Let me explain why. The description, in my case, is 100% accurate. It describes my changes in three blocks: change type, description, and the pull request walkthrough. See the image below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b2Ubzk03--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kpnikr649tasrvw59hv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b2Ubzk03--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kpnikr649tasrvw59hv1.png" alt="codium describe" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It even added a label to pinpoint what this pull request is about. Which is nice. This is opening some other possibilities. I would like to process a pull request based on its labels. Next time about that.&lt;/p&gt;

&lt;p&gt;But it did change the original title and removed the original description, even though I told it not to!? These two options should be set by default and not vice versa. And Codium PR-Agent should add its description and title to the original ones. Why? First, the person who created the pull request should have written the title and description to add some context and motivation, add a link to a ticketing system, etc. But ok, I can live with that at the moment. In the image above, I marked the yellow parts updated by Codium PR-Agent: removed and updated the title and description. And with green parts that are added as I expected: the label.&lt;/p&gt;
&lt;h5&gt;
  
  
  Generate Pull Request Review
&lt;/h5&gt;

&lt;p&gt;Ok, let's move on to the next command. This command will generate a pull request review. To do this, run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pragent review

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pWmQHQIU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v23nchrg6e8eeoxad5hs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pWmQHQIU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v23nchrg6e8eeoxad5hs.png" alt="codioum review" width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using the default configuration gives me nice results. Everything that needs to be addressed in a green box is nicely marked. The yellow box is why I wanted to keep the original title and description. The CodiumAI PR-Agent should just add its description next to the original one.&lt;/p&gt;

&lt;h5&gt;
  
  
  Give Me Some Suggestions
&lt;/h5&gt;

&lt;p&gt;Let's see what Codium PR-Agent can suggest to me. Since changes are simple, I'm going with the default configuration. To do this, run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pragent improve

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NrU-DOnl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r0anfqi8222fn3e6gqwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NrU-DOnl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r0anfqi8222fn3e6gqwx.png" alt="codium improve" width="800" height="644"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have got three suggestions. Above, you just see one. Even if the cases are straightforward, suggestions are on point as clean code suggestions. I can accept or reject them as in any other pull request.&lt;/p&gt;

&lt;h5&gt;
  
  
  Generate Changelog And Update Docs
&lt;/h5&gt;

&lt;p&gt;These two commands are interesting to me. Let's start with the changelog. To do this, run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pragent update_changelog

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fO_0Ny1x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8wuqzajs7tbrpwfqsjfp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fO_0Ny1x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8wuqzajs7tbrpwfqsjfp.png" alt="codium changelog" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running with the default configuration gives me a nice changelog. Even if I did not have one, it created one for me. However, it did not update the pull request. The reason is apparent: I did not instruct it to do so. But CodiumAI PR-Agent is smart enough to tell me so and give me instructions on how to do it. So I do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pragent update_changelog &lt;span class="nt"&gt;--pr_update_changelog&lt;/span&gt;.push_changelog_changes&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Excellent, let us try updating docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pragent add_docs

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--K6iwVeiG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1m6ecrglkj0rhsi1wpcq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--K6iwVeiG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1m6ecrglkj0rhsi1wpcq.png" alt="codium docs" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They are added as code improvements. I can accept them or reject them as in any other pull request. Nice.&lt;/p&gt;

&lt;h4&gt;
  
  
  Troubleshooting
&lt;/h4&gt;

&lt;p&gt;You can find documentation on &lt;a href="https://github.com/Codium-ai/pr-agent"&gt;CodiumAI PR-Agent GitHub repository&lt;/a&gt; is quite good. You'll find most of your answers there. I want to point to one thing related to giving the correct permissions to CodiumAI PR-Agent when creating a GitHub API token. Which permissions you need to give depends on what you want to do with it.&lt;/p&gt;

&lt;p&gt;For any integration, you should give CodiumAI PR-Agent only the permissions it needs to do its job. If you give it less permissions than it needs, it will not work. I'm fond of the principle of least privilege. So, I gave it only the permissions it needed to do its job.&lt;/p&gt;

&lt;p&gt;How will you now? Use the CLI and see what it says. Here is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pragent update_changelog &lt;span class="nt"&gt;--pr_update_changelog&lt;/span&gt;.push_changelog_changes&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true

&lt;/span&gt;2023-12-10 18:20:45.902 | INFO     | pr_agent.algo.utils:update_settings_from_args:275 - Updated setting PR_UPDATE_CHANGELOG.PUSH_CHANGELOG_CHANGES to: &lt;span class="s2"&gt;"True"&lt;/span&gt;
2023-12-10 18:20:47.768 | INFO     | pr_agent.tools.pr_update_changelog:_get_changlog_file:152 - No CHANGELOG.md file found &lt;span class="k"&gt;in &lt;/span&gt;the repository. Creating one...
Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"/app/pr_agent/tools/pr_update_changelog.py"&lt;/span&gt;, line 144, &lt;span class="k"&gt;in &lt;/span&gt;_get_changlog_file
...snip...
github.GithubException.UnknownObjectException: 404 &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;: &lt;span class="s2"&gt;"Not Found"&lt;/span&gt;, &lt;span class="s2"&gt;"documentation_url"&lt;/span&gt;: &lt;span class="s2"&gt;"https://docs.github.com/rest/repos/contents#get-repository-content"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

During handling of the above exception, another exception occurred:

Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"/app/pr_agent/cli.py"&lt;/span&gt;, line 68, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
...snip...
github.GithubException.GithubException: 403 &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;: &lt;span class="s2"&gt;"Resource not accessible by personal access token"&lt;/span&gt;, &lt;span class="s2"&gt;"documentation_url"&lt;/span&gt;: &lt;span class="s2"&gt;"https://docs.github.com/rest/repos/contents#create-or-update-file-contents"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did not give enough permissions to CodiumAI PR-Agent to update the changelog. So, it failed. I gave it the proper permissions, and it worked. Simple as that.&lt;br&gt;
&lt;strong&gt;I suggest you run all your scenarios with CLI first. It will save you a lot of time.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How to use Codium PR-Agent as GitHub Action
&lt;/h3&gt;

&lt;p&gt;Now that I have tried my scenarios with CLI, I want to automate them. I want to use Codium PR-Agent as GitHub Action. &lt;/p&gt;
&lt;h4&gt;
  
  
  Setup CodiumAI PR-Agent GitHub Action
&lt;/h4&gt;

&lt;p&gt;Thanks to the nice documentation on &lt;a href="https://github.com/Codium-ai/pr-agent/blob/main/INSTALL.md#run-as-a-github-action"&gt;CodiumAI PR-Agent GitHub repository&lt;/a&gt;, I do&lt;br&gt;
this with ease. I just need to add a new workflow file to my repository. I named it &lt;code&gt;codium-pr-agent.yml&lt;/code&gt;. Here is the content of the file:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PRAgent&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;issue_comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;created&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pr_agent_job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;issues&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run pr agent on every pull request, respond to user comments&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PR Agent action step&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pragent&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Codium-ai/pr-agent@main&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;OPENAI_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.OPENAI }}&lt;/span&gt;
          &lt;span class="na"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.API_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;pr_description.use_description_markers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;pr_description.include_generated_by_header&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;github_action.auto_review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;github_action.auto_describe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;github_action.auto_improve&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
          &lt;span class="na"&gt;pr_update_changelog.push_changelog_changes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Also, I added a pull request template to my repository in a &lt;code&gt;.github/pull_request_template.md&lt;/code&gt;. Here is the content of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Describe your changes&lt;/span&gt;

&lt;span class="gu"&gt;## Issue ticket number and link&lt;/span&gt;

&lt;span class="gu"&gt;## Checklist before requesting a review&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] I have performed a self-review of my code
&lt;span class="p"&gt;-&lt;/span&gt; [ ] If it is a core feature, I have added thorough tests.
&lt;span class="p"&gt;-&lt;/span&gt; [ ] I have commented my code, particularly in hard-to-understand areas
&lt;span class="p"&gt;-&lt;/span&gt; [ ] I have made corresponding changes to the documentation

&lt;span class="gu"&gt;## PR Description&lt;/span&gt;

pr_agent:summary

&lt;span class="gu"&gt;## PR Walkthrough&lt;/span&gt;

pr_agent:walkthrough
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And remember to add your API keys as secrets to your repository. I named them &lt;code&gt;OPENAI&lt;/code&gt; and &lt;code&gt;API_TOKEN&lt;/code&gt;. Add them to your repository by going to your repository's settings and then to Secrets. Click on Add a new secret and add your API keys. Read more on GitHub secrets &lt;a href="https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For additional CodiumAI-PR-Agent configuration options, you can check &lt;a href="https://github.com/Codium-ai/pr-agent/blob/main/pr_agent/settings/configuration.toml"&gt;CodiumAI PR-Agent GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The above configuration will run CodiumAI PR-Agent on every pull request, use the pull request template to generate a pull request description, create a pull request review, and give me some suggestions. Nice. I got all I wanted. Next time a pull request is made on my repository, CodiumAI PR-Agent will do its magic. I can quickly do future reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;p&gt;Even if all sounds nice, there is one gotcha. That is OpenAI. It is not free, and you'll need to pay for it. To reduce costs, you need to spend some time configuring CodiumAI PR-Agent, which does only what you need on a limited set of files, primarily if you use code generation.&lt;/p&gt;

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

&lt;p&gt;CodiumAI PR-Agent is an excellent tool. It can help you with your pull requests. It can generate a pull request description, review, give you some suggestions, update&lt;br&gt;
changelog and docs, and chit-chat with it if you want. See this nice &lt;a href="https://github.com/robert-nemet/klock/pull/49#issuecomment-1849084363"&gt;&lt;code&gt;/ask&lt;/code&gt; command and its answer&lt;/a&gt;. Check it yourself and see what it can do by setting up scenarios. It is worth it trying it.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this article. If you have any questions or comments, please leave them below. I'll be happy to answer them.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>githubactions</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Exploring GCP With Terraform: Adding Terragrunt</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Wed, 20 Sep 2023 08:44:36 +0000</pubDate>
      <link>https://forem.com/madmaxx/exploring-gcp-with-terraform-adding-terragrunt-1dd4</link>
      <guid>https://forem.com/madmaxx/exploring-gcp-with-terraform-adding-terragrunt-1dd4</guid>
      <description>&lt;p&gt;Finally, this is the last step in making the Terraform project manageable. I'll add &lt;a href="https://terragrunt.gruntwork.io/"&gt;Terragrunt&lt;/a&gt; to the project. Terragrunt is a thin wrapper for Terraform that provides extra tools for working with multiple Terraform modules. It's an excellent tool for making your Terraform code DRY and reusable.&lt;/p&gt;

&lt;p&gt;I'm starting with the same project as in &lt;a href="https://rnemet.dev/posts/gcp/gcp_tf_refactor/"&gt;the previous post&lt;/a&gt;. The current state of &lt;a href="https://github.com/robert-nemet/pcne/tree/add_modules"&gt;code is available here&lt;/a&gt; in the branch &lt;code&gt;add_modules&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Well, you need to install Terragrunt. You can find installation instructions &lt;a href="https://terragrunt.gruntwork.io/docs/getting-started/install/"&gt;here&lt;/a&gt;. That is all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Terragrunt to the back-office network and vms modules
&lt;/h2&gt;

&lt;p&gt;I'm starting with the &lt;code&gt;back-office&lt;/code&gt; module. What I do in this module will be easily copied to other modules.&lt;/p&gt;

&lt;p&gt;Notice that common code in &lt;code&gt;back-office/vms&lt;/code&gt; and &lt;code&gt;back-office/network&lt;/code&gt; is related to the provider and state file configurations. This code is common for all modules. The only thing that differs is the &lt;code&gt;backend.prefix&lt;/code&gt;. Its value differs from module to module.&lt;/p&gt;

&lt;p&gt;So, the first thing is to make a global configuration on the &lt;code&gt;dev&lt;/code&gt; level. To do that, I'm creating a &lt;code&gt;terragrunt.hcl&lt;/code&gt; file in the &lt;code&gt;dev&lt;/code&gt; folder:&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;generate&lt;/span&gt; &lt;span class="s2"&gt;"provider"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"provider.tf"&lt;/span&gt;
    &lt;span class="nx"&gt;if_exists&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"overwrite_terragrunt"&lt;/span&gt;
    &lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
provider "google" {
  project = var.project_id
  region  = var.region
  zone    = var.zone
}

terraform {
  required_version = "&amp;gt;=1.5.7"

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "4.77.0"
    }
  }
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;remote_state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gcs"&lt;/span&gt;
    &lt;span class="nx"&gt;generate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"backend.tf"&lt;/span&gt;
        &lt;span class="nx"&gt;if_exists&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"overwrite_terragrunt"&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="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-states-network-playground-382512"&lt;/span&gt;
        &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform/state/dev/${path_relative_to_include()}"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;extra_arguments&lt;/span&gt; &lt;span class="s2"&gt;"common"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;commands&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;get_terraform_commands_that_need_vars&lt;/span&gt;&lt;span class="err"&gt;()&lt;/span&gt;

        &lt;span class="nx"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;"-var-file=${path_relative_from_include()}/common.tfvars"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file has three sections. The first section is &lt;code&gt;generate "provider"&lt;/code&gt;. This section will generate a &lt;code&gt;provider.tf&lt;/code&gt; file in each module. Suppose it already exists and is managed by Terragrunt. In that case, it will be re-generated if there are any changes to the configuration. This file will contain provider configuration.&lt;/p&gt;

&lt;p&gt;The second section is &lt;code&gt;remote_state&lt;/code&gt;. This section will generate a &lt;code&gt;backend.tf&lt;/code&gt; file in each module. This file will contain state file configuration. Notice that &lt;code&gt;prefix&lt;/code&gt; is set to &lt;code&gt;terraform/state/dev/${path_relative_to_include()}&lt;/code&gt;. This setting will store the state file in the &lt;code&gt;terraform/state/dev&lt;/code&gt; folder, and each module will have its subfolder. That is important because each module will have its state file.&lt;/p&gt;

&lt;p&gt;The third section is &lt;code&gt;terraform&lt;/code&gt;. This section will add extra arguments to Terraform commands. In this case, it will add &lt;code&gt;-var-file=${path_relative_from_include()}/common.tfvars&lt;/code&gt;. So, when I run any Terraform command requiring variables, Terragrunt will add this argument. That allows me to have one &lt;code&gt;common.tfvars&lt;/code&gt; file in the &lt;code&gt;dev&lt;/code&gt; folder, and all modules will use it.&lt;/p&gt;

&lt;p&gt;Now, I need to add the &lt;code&gt;common.tfvars&lt;/code&gt; file to the &lt;code&gt;dev&lt;/code&gt; folder:&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;project_id&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"network-playground-382512"&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"europe-west1"&lt;/span&gt;
&lt;span class="nx"&gt;zone&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"europe-west1-b"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I need to add a &lt;code&gt;terragrunt.hcl&lt;/code&gt; file to each module to use this. I'm adding it to the &lt;code&gt;back-office&lt;/code&gt; module:&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;include&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;find_in_parent_folders&lt;/span&gt;&lt;span class="err"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I can remove the &lt;code&gt;provider.tf&lt;/code&gt; and &lt;code&gt;terraform.tfvars&lt;/code&gt; files from the &lt;code&gt;back-office&lt;/code&gt; module. This time, I'll be running &lt;code&gt;terragrunt plain&lt;/code&gt; instead of &lt;code&gt;terraform plan&lt;/code&gt; in the &lt;code&gt;back-office&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terragrunt plan
Acquiring state lock. This may take a few moments...
google_service_account.back_office_fw_sa: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects/network-playground-382512/serviceAccounts/back-office@network-playground-382512.iam.gserviceaccount.com]
module.back-office.module.vpc.google_compute_network.network: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects/network-playground-382512/global/networks/back-office]
module.back-office.module.subnets.google_compute_subnetwork.subnetwork[&lt;span class="s2"&gt;"us-central1/back-office-private"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects/network-playground-382512/regions/us-central1/subnetworks/back-office-private]
module.back-office.module.subnets.google_compute_subnetwork.subnetwork[&lt;span class="s2"&gt;"us-central1/back-office"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects/network-playground-382512/regions/us-central1/subnetworks/back-office]
module.back-office.module.firewall_rules.google_compute_firewall.rules_ingress_egress[&lt;span class="s2"&gt;"back-office-icmp"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects/network-playground-382512/global/firewalls/back-office-icmp]
module.back-office.module.firewall_rules.google_compute_firewall.rules_ingress_egress[&lt;span class="s2"&gt;"back-office-iap"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects/network-playground-382512/global/firewalls/back-office-iap]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the &lt;code&gt;terragrunt plan&lt;/code&gt; in the &lt;code&gt;back-office/network&lt;/code&gt; module will generate two files: &lt;code&gt;provider.tf&lt;/code&gt; and &lt;code&gt;backend.tf&lt;/code&gt;, according to the &lt;code&gt;terragrunt.hcl&lt;/code&gt; file in the &lt;code&gt;dev&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;back-office/vms&lt;/code&gt; module, I need to add the &lt;code&gt;terragrunt.hcl&lt;/code&gt; file to the &lt;code&gt;back-office/vms&lt;/code&gt; folder:&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;include&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;find_in_parent_folders&lt;/span&gt;&lt;span class="err"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"../network"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dependency&lt;/span&gt; &lt;span class="s2"&gt;"network"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../network"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_back_office_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependency&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_back_office_id&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_back_office_subnetwork&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependency&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_back_office_subnetwork&lt;/span&gt;
  &lt;span class="nx"&gt;back_office_fw_sa&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependency&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office_fw_sa&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, this &lt;code&gt;terragrunt.hcl&lt;/code&gt; not only includes files from parent folders but also defines dependency on the &lt;code&gt;../network&lt;/code&gt; module. That means that the &lt;code&gt;back-office/vms&lt;/code&gt; module depends on the &lt;code&gt;back-office/network&lt;/code&gt; module. A network needs to be created before virtual machines. That is important because the &lt;code&gt;back-office/vms&lt;/code&gt; module needs to know some outputs from the &lt;code&gt;back-office/network&lt;/code&gt; module. The section &lt;code&gt;inputs&lt;/code&gt; defines inputs for the &lt;code&gt;back-office/vms&lt;/code&gt; module. These inputs are outputs from the &lt;code&gt;back-office/network&lt;/code&gt; module. The last thing allows me to remove in addition to &lt;code&gt;provider.tf&lt;/code&gt; and &lt;code&gt;terraform.tfvars&lt;/code&gt;, and &lt;code&gt;inputs.tf&lt;/code&gt; files from the &lt;code&gt;back office/vms&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;I need to add additional variables to the &lt;code&gt;variables.tf&lt;/code&gt; file in the &lt;code&gt;back-office/vms&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;"back_office_fw_sa"&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;"back office firewall service account"&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;"vpc_back_office_id"&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;"back office vpc id"&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;"vpc_back_office_subnetwork"&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;"back office vpc subnetwork"&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;any&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;terragrunt plan&lt;/code&gt; should give me no changes needed. Also, I can now run &lt;code&gt;terragrunt apply&lt;/code&gt; instead of &lt;code&gt;terraform apply&lt;/code&gt; in &lt;code&gt;back-office/vms&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;What I did in the &lt;code&gt;back-office&lt;/code&gt; module, I need to do in the &lt;code&gt;service&lt;/code&gt; and &lt;code&gt;strorage&lt;/code&gt; modules. Simple. Remember to properly define dependencies and inputs in other &lt;code&gt;vms&lt;/code&gt; modules.&lt;/p&gt;

&lt;p&gt;When it comes to the &lt;code&gt;peering&lt;/code&gt; module, I need to add the &lt;code&gt;terragrunt.hcl&lt;/code&gt; file to the &lt;code&gt;peering&lt;/code&gt; folder:&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;include&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;find_in_parent_folders&lt;/span&gt;&lt;span class="err"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;paths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"../back-office/network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"../services/network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"../storage/network"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dependency&lt;/span&gt; &lt;span class="s2"&gt;"back-office"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../back-office/network"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dependency&lt;/span&gt; &lt;span class="s2"&gt;"services"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../services/network"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dependency&lt;/span&gt; &lt;span class="s2"&gt;"storage"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;config_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"../storage/network"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_back_office&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependency&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_back_office&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependency&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_storage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dependency&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_storage&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will ensure that the &lt;code&gt;peering&lt;/code&gt; module will be created after the &lt;code&gt;back-office&lt;/code&gt;, &lt;code&gt;services&lt;/code&gt;, and &lt;code&gt;storage&lt;/code&gt; modules. Also, it will provide inputs for the &lt;code&gt;peering&lt;/code&gt; module. These inputs are outputs from &lt;code&gt;back-office&lt;/code&gt;, &lt;code&gt;services&lt;/code&gt;, and &lt;code&gt;storage&lt;/code&gt; modules.&lt;/p&gt;

&lt;p&gt;Interesting would be generating a graphical representation of the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terragrunt graph-dependencies | dot &lt;span class="nt"&gt;-Tpng&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dependencies.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="/images/dependencies.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/dependencies.png" alt="dependencies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;terragrunt graph-dependencies&lt;/code&gt; command will generate a graph of the dependencies between modules. The &lt;code&gt;dot -Tpng &amp;gt; dependencies.png&lt;/code&gt; command will convert the graph to a PNG file, that is &lt;a href="https://graphviz.org/"&gt;graphviz&lt;/a&gt;. That is a great way to visualize dependencies between modules.&lt;/p&gt;

&lt;p&gt;Try from &lt;code&gt;dev&lt;/code&gt; folder &lt;code&gt;terragrunt run-all plan&lt;/code&gt;. The Terragrunt will split the modules into groups. In each group, it will run &lt;code&gt;plan&lt;/code&gt;, but first, it will run it in &lt;code&gt;network&lt;/code&gt; modules because they are dependencies for other modules. Then, it will run &lt;code&gt;plan&lt;/code&gt; in parallel for other modules in the other group.&lt;/p&gt;

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

&lt;p&gt;Adding Terragrunt to the project is relatively easy. It requires some changes to the project structure and the code. But it is worth it. It makes the project more manageable: code is DRY, and common code is generated in one place. I can run Terraform commands from the root folder and do not need to visit each module, etc...&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://terragrunt.gruntwork.io/"&gt;Terragrunt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>terragrunt</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>GCP With Terraform: Refactor with Modules</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Wed, 13 Sep 2023 20:51:33 +0000</pubDate>
      <link>https://forem.com/madmaxx/gcp-with-terraform-refactor-with-modules-5da8</link>
      <guid>https://forem.com/madmaxx/gcp-with-terraform-refactor-with-modules-5da8</guid>
      <description>&lt;p&gt;This post is the fourth part of the series about using Terraform to manage GCP resources. In the &lt;a href="https://rnemet.dev/posts/gcp/gcp_tf_vpc/"&gt;first part&lt;/a&gt;, I did a basic setup of the project: remote state file, state file encryption, a bucket creation. In the &lt;a href="https://rnemet.dev/posts/gcp/gcp-tf-vpc/"&gt;second part&lt;/a&gt;, I created VPC and subnets and added some basic firewall rules and VMs. In the &lt;a href="https://rnemet.dev/posts/gcp/gcp-tf-vpc-firewall_2/"&gt;third part&lt;/a&gt;, I added more VPC, subnets, firewall rules, and VMs. In this part, I will refactor the project to make it more manageable. It should be more DRY and easier to maintain while supporting multiple environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current State
&lt;/h2&gt;

&lt;p&gt;You can look at the project's current state &lt;a href="https://github.com/robert-nemet/pcne/tree/start_refactoring"&gt;here&lt;/a&gt;. The existing file structure is like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── README.md
├── Taskfile.yml
└── gcp
    ├── base
    │   ├── .terraform-version
    │   ├── .terraform.lock.hcl
    │   ├── README.md
    │   ├── main.tf
    │   ├── outputs.tf
    │   ├── provider.tf
    │   ├── terraform.tfvars
    │   └── variables.tf
    ├── network
    │   ├── .terraform-version
    │   ├── .terraform.lock.hcl
    │   ├── README.md
    │   ├── main.tf
    │   ├── outputs.tf
    |   ├── peering.tf
    │   ├── provider.tf
    │   ├── terraform.tfvars
    |   ├── vpc_back_office.tf
    │   ├── vpc_datastorages.tf
    |   ├── vpc_services.tf
    │   └── variables.tf
    └── vms
        ├── .terraform-version
        ├── .terraform.lock.hcl
        ├── README.md
        ├── main.tf
        ├── back_office.tf
        ├── imports.tf
        ├── outputs.tf
        ├── provider.tf
        ├── terraform.tfvars
        └── variables.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;base&lt;/code&gt; directory contains the basic setup of the project. It creates a bucket for the remote state file, enabling the state file's encryption. The &lt;code&gt;network&lt;/code&gt; directory contains the VPC, subnets, and firewall rules. The &lt;code&gt;vms&lt;/code&gt; directory contains the VMs. The &lt;code&gt;Taskfile.yml&lt;/code&gt; is used to run the tasks. The &lt;code&gt;README.md&lt;/code&gt; contains the documentation of the project.&lt;/p&gt;

&lt;p&gt;There are several problems with this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modules &lt;code&gt;network&lt;/code&gt; and &lt;code&gt;vms&lt;/code&gt; handle more than they should. For example, the &lt;code&gt;network&lt;/code&gt; module creates VPC, subnets, and firewall rules for all VPCs.
It should handle only one VPC. This way, whenever I make a change in one VPC when I run &lt;code&gt;terraform plan/apply&lt;/code&gt; in the module &lt;code&gt;network&lt;/code&gt; other VPCs will be touched. If I have
to work with others, this is the possible bottleneck. The &lt;code&gt;vms&lt;/code&gt; module is creating VMs for all VPCs.&lt;/li&gt;
&lt;li&gt;There is a lot of code duplication.&lt;/li&gt;
&lt;li&gt;This structure does not support multiple environments. I can't have a dev and prod environment with this setup.&lt;/li&gt;
&lt;li&gt;Changing one module requires running &lt;code&gt;terraform plan/apply&lt;/code&gt; in all modules(not always). For example, when adding a service account to the VMs, I had to
run &lt;code&gt;terraform plan/apply&lt;/code&gt; in the &lt;code&gt;network&lt;/code&gt; and &lt;code&gt;vms&lt;/code&gt; modules. It is not a big deal, but it is annoying.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step one: Split into environments
&lt;/h2&gt;

&lt;p&gt;The first step is to split the project into environments. I will create two environments: &lt;code&gt;dev&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt;. I will create two directories: &lt;code&gt;dev&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt; on the project level. Then, I will copy whole &lt;code&gt;gcp&lt;/code&gt; directory into both &lt;code&gt;dev&lt;/code&gt; and &lt;code&gt;prod&lt;/code&gt; directories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── README.md
├── Taskfile.yml
├── dev
│   ├── base
│   ├── network
│   └── vms
└── prod
    ├── base
    ├── network
    └── vms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? With this change, I'm reducing the impact of changes. While in an ideal case, these environments should be identical, they are not. To be honest, you do not need the same level of resources in dev and prod.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step two: Create a module for each VPC
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;network&lt;/code&gt; module is doing too much. It is creating VPC, subnets, and firewall rules for all VPCs. It will be much better if it makes only one VPC, subnets, and firewall rules for that VPC. But the same thing applies to the &lt;code&gt;vms&lt;/code&gt; module. It is creating VMs for all VPCs. It will be much better if it makes VMs for only one VPC.&lt;/p&gt;

&lt;p&gt;If I put network stuff and VMs in the same folder, which will then contain &lt;code&gt;network&lt;/code&gt; and &lt;code&gt;vms&lt;/code&gt; modules, for each VPC, I can create a VPC, subnets, firewall rules, and VMs for that VPC. This way, I'm separating the network stuff and VMs for each VPC. But, at the same time, I'm keeping them together. This way, I'm reducing the impact of changes. So, my new file structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── README.md
├── Taskfile.yml
├── dev
│   ├── base
│   ├── back-office
│   │   ├── network
│   │   └── vms
│   ├── services
│   │   ├── network
│   │   └── vms
│   └── storage
│       ├── network
│       └── vms
└── prod
    ├── base
    ├── back-office
    │   ├── network
    │   └── vms
    ├── services
    │   ├── network
    │   └── vms
    └── storage
        ├── network
        └── vms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to this change, I'll keep code related to the target VPC. This change simplifies the code, increases readability, and reduces the impact of changes. But at the same time, it increases the number of modules, and there is an increase in code duplication. Before dealing with code duplication, I'll ensure the code is working and has the same&lt;br&gt;
structure.&lt;/p&gt;

&lt;p&gt;What does that mean? Each module will have the same structure: &lt;code&gt;main.tf&lt;/code&gt;, &lt;code&gt;outputs.tf&lt;/code&gt;, &lt;code&gt;provider.tf&lt;/code&gt;, &lt;code&gt;terraform.tfvars&lt;/code&gt;, and &lt;code&gt;variables.tf&lt;/code&gt;. The &lt;code&gt;main.tf&lt;/code&gt; will contain the code for creating resources. The &lt;code&gt;outputs.tf&lt;/code&gt; will contain the outputs of the module. The &lt;code&gt;provider.tf&lt;/code&gt; will contain the provider configuration. The &lt;code&gt;terraform.tfvars&lt;/code&gt; will contain the variables for the module. The &lt;code&gt;variables.tf&lt;/code&gt; will contain the variables for the module.&lt;/p&gt;
&lt;h3&gt;
  
  
  Refactoring the base module
&lt;/h3&gt;

&lt;p&gt;This module is the most straightforward module to refactor. There is little to change. Except the where the state file is stored. It's done by changing the &lt;code&gt;backend.prefix&lt;/code&gt; in:&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;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;=1.5.5"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google"&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;"4.77.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"gcs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-states-network-playground-382512"&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform/state/dev/base"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The state file for the &lt;code&gt;base&lt;/code&gt; module will be in the &lt;code&gt;terraform/state/dev/base&lt;/code&gt; directory. The previous state file was in the &lt;code&gt;terraform/state/base&lt;/code&gt;. How do I move the state file? Manually? It can be done. But there is a better way. If I run the &lt;code&gt;terraform plan&lt;/code&gt; in the &lt;code&gt;base&lt;/code&gt; module, I will get the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform plan
╷
│ Error: Backend initialization required: please run &lt;span class="s2"&gt;"terraform init"&lt;/span&gt;
│
│ Reason: Backend configuration block has changed
│
│ The &lt;span class="s2"&gt;"backend"&lt;/span&gt; is the interface that Terraform uses to store state,
│ perform operations, etc. If this message is showing up, it means that the
│ Terraform configuration you&lt;span class="s1"&gt;'re using is using a custom configuration for
│ the Terraform backend.
│
│ Changes to backend configurations require reinitialization. This allows
│ Terraform to set up the new configuration, copy existing state, etc. Please run
│ "terraform init" with either the "-reconfigure" or "-migrate-state" flags to
│ use the current configuration.
│
│ If the change reason above is incorrect, please verify your configuration
│ hasn'&lt;/span&gt;t changed and try again. At this point, no changes to your existing
│ configuration or state have been made.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the output clearly says, I must run &lt;code&gt;terraform init&lt;/code&gt; with either the &lt;code&gt;-reconfigure&lt;/code&gt; or &lt;code&gt;-migrate-state&lt;/code&gt; flags. I'll use the &lt;code&gt;-migrate-state&lt;/code&gt; flag. This flag will migrate the state file to the new location. So, I'll run &lt;code&gt;terraform init -migrate-state&lt;/code&gt; in the &lt;code&gt;base&lt;/code&gt; module. The output will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform init &lt;span class="nt"&gt;-migrate-state&lt;/span&gt;

Initializing the backend...
Backend configuration changed!

Terraform has detected that the configuration specified &lt;span class="k"&gt;for &lt;/span&gt;the backend
has changed. Terraform will now check &lt;span class="k"&gt;for &lt;/span&gt;existing state &lt;span class="k"&gt;in &lt;/span&gt;the backends.

Acquiring state lock. This may take a few moments...
Acquiring state lock. This may take a few moments...
Do you want to copy existing state to the new backend?
  Pre-existing state was found &lt;span class="k"&gt;while &lt;/span&gt;migrating the previous &lt;span class="s2"&gt;"gcs"&lt;/span&gt; backend to the
  newly configured &lt;span class="s2"&gt;"gcs"&lt;/span&gt; backend. No existing state was found &lt;span class="k"&gt;in &lt;/span&gt;the newly
  configured &lt;span class="s2"&gt;"gcs"&lt;/span&gt; backend. Do you want to copy this state to the new &lt;span class="s2"&gt;"gcs"&lt;/span&gt;
  backend? Enter &lt;span class="s2"&gt;"yes"&lt;/span&gt; to copy and &lt;span class="s2"&gt;"no"&lt;/span&gt; to start with an empty state.

  Enter a value: &lt;span class="nb"&gt;yes


&lt;/span&gt;Successfully configured the backend &lt;span class="s2"&gt;"gcs"&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Reusing previous version of hashicorp/google from the dependency lock file
- Using previously-installed hashicorp/google v4.77.0

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running &lt;span class="s2"&gt;"terraform plan"&lt;/span&gt; to see
any changes that are required &lt;span class="k"&gt;for &lt;/span&gt;your infrastructure. All Terraform commands
should now work.

If you ever &lt;span class="nb"&gt;set &lt;/span&gt;or change modules or backend configuration &lt;span class="k"&gt;for &lt;/span&gt;Terraform,
rerun this &lt;span class="nb"&gt;command &lt;/span&gt;to reinitialize your working directory. If you forget, other
commands will detect it and remind you to &lt;span class="k"&gt;do &lt;/span&gt;so &lt;span class="k"&gt;if &lt;/span&gt;necessary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running &lt;code&gt;terrafom plan&lt;/code&gt; should work, giving you &lt;strong&gt;no changes&lt;/strong&gt; as output.&lt;/p&gt;

&lt;p&gt;With this, the base is checked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refactoring the network module
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;network&lt;/code&gt; module is a bit more complicated to refactor. Initially, it created VPCs, subnets, and firewall rules for all VPCs. Now, it will create VPC, subnets, and firewall rules&lt;br&gt;
for only one VPC. That means that I have to split changes for each VPC. This is the easy part. A simple copy-paste technique will do the trick. But what about the state file? Let's look at the first VPC: &lt;code&gt;back-office&lt;/code&gt; for the &lt;code&gt;dev&lt;/code&gt; environment:&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="err"&gt;...&lt;/span&gt;

  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"gcs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-states-network-playground-382512"&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform/state/dev/back-office/network"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The state file for the &lt;code&gt;network&lt;/code&gt; module will be in the &lt;code&gt;terraform/state/dev/network/back-office&lt;/code&gt; directory. The previous state file was in the &lt;code&gt;terraform/state/network&lt;/code&gt;. I can repeat the same technique as I did for the &lt;code&gt;base&lt;/code&gt; module. However, I plan to remove all code related to the other VPCs. When I remove code related to the other VPCs, the Terraform&lt;br&gt;
will say that it will delete resources related to other VPCs. Why? It is simple: the state file for the &lt;code&gt;network&lt;/code&gt; module contains resources related to other VPCs. So, when I remove code related to other VPCs, Terraform will see that the state file includes resources related to other VPCs and try to delete them. I want something else. I want to keep the resources related to other VPCs. So before I clean up the code, I'll move the state file with the &lt;code&gt;-migrate-state&lt;/code&gt; flag. I'll run &lt;code&gt;terraform init -migrate-state&lt;/code&gt; in the &lt;code&gt;dev/back-office/network&lt;/code&gt; module. The Terraform will migrate the state file. If you look into the bucket, you will see that the ole state file is copied to the &lt;code&gt;terraform/state/dev/network/back-office&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gsutil &lt;span class="nb"&gt;ls &lt;/span&gt;gs://terraform-states-network-playground-382512/terraform/state/network
gs://terraform-states-network-playground-382512/terraform/state/network/default.tfstate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, the &lt;code&gt;-migration-state&lt;/code&gt; flag does not delete the old state file but copies it to the new location. This makes life easier. After Terraform migrates the state file, I can clean up the code. I'll remove all code related to other VPCs.&lt;/p&gt;

&lt;p&gt;The VPC peering is one thing that is related to all VPCs, and I need to refactor all of them before I can deal with it. I'll name a separate module in the dev environment &lt;code&gt;peering&lt;/code&gt;. This module will contain the code for creating VPC peering for all VPCs. For now, it should be a copy of the old &lt;code&gt;network&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;To continue with the refactoring &lt;code&gt;back-office&lt;/code&gt; VPC, I'll remove all code related to other VPCs. When I run &lt;code&gt;terraform plan&lt;/code&gt; this time, I'll get the following output as expected. I see that Terraform will delete resources related to other VPCs. I see the message &lt;code&gt;Plan: 0 to add, 0 to change, 12 to destroy.&lt;/code&gt; So, I want to keep the resources related to other VPCs but to delete them from the state file related to the VPC &lt;code&gt;back-office&lt;/code&gt; network module. I can do that by running the &lt;code&gt;terraform state rm&lt;/code&gt; command. I'll run &lt;code&gt;terraform state rm&lt;/code&gt; for each resource related to other VPCs. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform state &lt;span class="nb"&gt;rm &lt;/span&gt;google_compute_subnetwork.subnet_postgres
Acquiring state lock. This may take a few moments...
Removed google_compute_subnetwork.subnet_postgres
Successfully removed 1 resource instance&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So there is a lot of typing. You can use more than one resource when removing resources from the state file. I do this until I get the message that there is nothing to change. All of this I do for the other VPCs in the dev environment.&lt;/p&gt;

&lt;p&gt;To refactor the &lt;code&gt;dev/peerings&lt;/code&gt; module, I'll do the same thing as I did for the &lt;code&gt;dev/back-office/network&lt;/code&gt; module. I'll migrate the state file and remove all code related to VPCs, except peerings. I have to import the VPC information for each VPC from their state files:&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;data&lt;/span&gt; &lt;span class="s2"&gt;"terraform_remote_state"&lt;/span&gt; &lt;span class="s2"&gt;"back_office"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gcs"&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="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-states-network-playground-382512"&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform/state/dev/back-office/network"&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;"terraform_remote_state"&lt;/span&gt; &lt;span class="s2"&gt;"services"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gcs"&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="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-states-network-playground-382512"&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform/state/dev/services/network"&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;"terraform_remote_state"&lt;/span&gt; &lt;span class="s2"&gt;"storage"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"gcs"&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="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-states-network-playground-382512"&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform/state/dev/storage/network"&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;Of course, those are exported in the &lt;code&gt;outputs. tf&lt;/code&gt; file for each VPC:&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;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_services"&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;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_services_subnetwork"&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;google_compute_subnetwork&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Refactoring the vms module
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;vms&lt;/code&gt; module situation resembles the &lt;code&gt;network&lt;/code&gt; module. The only difference is that I must import the VPC information from the &lt;code&gt;network&lt;/code&gt; modules. So, first, I'll migrate the state file, remove all code related to other VPCs, and then import the VPC information from the &lt;code&gt;network&lt;/code&gt; module. I'll do this for each VPC in the dev environment.&lt;/p&gt;

&lt;p&gt;See the final code &lt;a href="https://github.com/robert-nemet/pcne/tree/split_enviroments_vpcs"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When done, inspect the code. Notice how much there is duplicate code or almost the same code. Ignore resource names and a number of resources. That means that I can reuse the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step three: Reuse the code with modules
&lt;/h2&gt;

&lt;p&gt;There are a lot of code that is duplicated or almost the same. I want to make my code DRY. I can do it either by using &lt;a href="https://registry.terraform.io/modules/terraform-google-modules/network/google/latest"&gt;Google &lt;code&gt;network&lt;/code&gt; module&lt;/a&gt; or writing my module. It is wiser to use the already existing &lt;a href="https://registry.terraform.io/modules/terraform-google-modules/network/google/latest"&gt;Google &lt;code&gt;network&lt;/code&gt; module&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Refactoring the &lt;code&gt;dev/back-office/network&lt;/code&gt; module to use the &lt;a href="https://registry.terraform.io/modules/terraform-google-modules/network/google/latest"&gt;Google &lt;code&gt;network&lt;/code&gt; module&lt;/a&gt; is straightforward:&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;"back-office"&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;"terraform-google-modules/network/google"&lt;/span&gt;
  &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 7.3"&lt;/span&gt;

  &lt;span class="nx"&gt;project_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;network_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"back-office"&lt;/span&gt;
  &lt;span class="nx"&gt;routing_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"REGIONAL"&lt;/span&gt;


  &lt;span class="nx"&gt;subnets&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;subnet_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"back-office"&lt;/span&gt;
      &lt;span class="nx"&gt;subnet_ip&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.1.0.0/24"&lt;/span&gt;
      &lt;span class="nx"&gt;subnet_region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;subnet_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"back-office-private"&lt;/span&gt;
      &lt;span class="nx"&gt;subnet_ip&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.2.0.0/24"&lt;/span&gt;
      &lt;span class="nx"&gt;subnet_region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;ingress_rules&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;"back-office-icmp"&lt;/span&gt;
      &lt;span class="nx"&gt;allow&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;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"icmp"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"back-office-iap"&lt;/span&gt;
      &lt;span class="nx"&gt;allow&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;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;"35.235.240.0/20"&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;target_service_accounts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;google_service_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office_fw_sa&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="nx"&gt;allow&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;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
        &lt;span class="p"&gt;}&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;google_service_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office_fw_sa&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that now I am not creating resources directly. Instead, I am calling the module and passing variables to it. That is the way to reuse code in Terraform.&lt;/p&gt;

&lt;p&gt;This code is more readable, and it is easier to maintain. But, as in the previous step, working with the state file is tricky. This time, I'll remove all other resources from the code. Since I have added the module, I need to run &lt;code&gt;terraform init&lt;/code&gt; and then &lt;code&gt;terraform plan&lt;/code&gt;. The plan will tell me that there are five resources to add and five to destroy. But I do not want to destroy anything. What I need to do is to rename the resources in the state file. I'll do that by running the &lt;code&gt;terraform state mv&lt;/code&gt; command. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform state &lt;span class="nb"&gt;mv &lt;/span&gt;google_compute_network.back_office  module.back-office.module.vpc.google_compute_network.network
Acquiring state lock. This may take a few moments...
Move &lt;span class="s2"&gt;"google_compute_network.back_office"&lt;/span&gt; to &lt;span class="s2"&gt;"module.back-office.module.vpc.google_compute_network.network"&lt;/span&gt;
Successfully moved 1 object&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;terraform state &lt;span class="nb"&gt;mv &lt;/span&gt;google_compute_subnetwork.back_office_private &lt;span class="s1"&gt;'module.back-office.module.subnets.google_compute_subnetwork.subnetwork["us-central1/back-office-private"]'&lt;/span&gt;
Move &lt;span class="s2"&gt;"google_compute_subnetwork.back_office_private"&lt;/span&gt; to &lt;span class="s2"&gt;"module.back-office.module.subnets.google_compute_subnetwork.subnetwork[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;us-central1/back-office-private&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt;
Successfully moved 1 object&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;terraform state &lt;span class="nb"&gt;mv &lt;/span&gt;google_compute_subnetwork.back_office &lt;span class="s1"&gt;'module.back-office.module.subnets.google_compute_subnetwork.subnetwork["us-central1/back-office"]'&lt;/span&gt;
Move &lt;span class="s2"&gt;"google_compute_subnetwork.back_office"&lt;/span&gt; to &lt;span class="s2"&gt;"module.back-office.module.subnets.google_compute_subnetwork.subnetwork[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;us-central1/back-office&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt;
Successfully moved 1 object&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Releasing state lock. This may take a few moments...

&lt;span class="nv"&gt;$ &lt;/span&gt;terraform state &lt;span class="nb"&gt;mv &lt;/span&gt;google_compute_firewall.back_office_icmp &lt;span class="s1"&gt;'module.back-office.module.firewall_rules.google_compute_firewall.rules_ingress_egress["back-office-icmp"]'&lt;/span&gt;
Move &lt;span class="s2"&gt;"google_compute_firewall.back_office_icmp"&lt;/span&gt; to &lt;span class="s2"&gt;"module.back-office.module.firewall_rules.google_compute_firewall.rules_ingress_egress[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;back-office-icmp&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt;
Successfully moved 1 object&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Releasing state lock. This may take a few moments...

&lt;span class="nv"&gt;$ &lt;/span&gt;terraform state &lt;span class="nb"&gt;mv &lt;/span&gt;google_compute_firewall.back_office_iap &lt;span class="s1"&gt;'module.back-office.module.firewall_rules.google_compute_firewall.rules_ingress_egress["back-office-iap"]'&lt;/span&gt;
Move &lt;span class="s2"&gt;"google_compute_firewall.back_office_iap"&lt;/span&gt; to &lt;span class="s2"&gt;"module.back-office.module.firewall_rules.google_compute_firewall.rules_ingress_egress[&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;back-office-iap&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]"&lt;/span&gt;
Successfully moved 1 object&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Releasing state lock. This may take a few moments...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As well, I want to retain the same outputs, so I refactor the &lt;code&gt;outputs.tf&lt;/code&gt; 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;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_back_office"&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;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network_self_link&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_back_office_id"&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;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_back_office_subnetwork"&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;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"us-central1/back-office"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_back_office_private_subnetwork"&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;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"us-central1/back-office-private"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"back_office_fw_sa"&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;google_service_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office_fw_sa&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, I can run the &lt;code&gt;terraform plan&lt;/code&gt; and see no changes. I can do the same for the other VPCs in the dev environment.&lt;/p&gt;

&lt;p&gt;If you wonder how to know which resource to rename, you can run &lt;code&gt;terraform plan&lt;/code&gt; and see which resources will be destroyed. Those are the resources that you need to rename. Names of new resources are new names of old resources.&lt;/p&gt;

&lt;p&gt;Terraform will not allow you to rename resources of different types. So, you can't rename a subnet to a firewall rule. You can rename a subnet to a subnet and a firewall rule to a firewall rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reuse code in the vms module
&lt;/h3&gt;

&lt;p&gt;In the previous step, I used the already provided module. But I can write my module, too. For managing VMs, I'll do it. I'll create the ' vms' module in the &lt;code&gt;&amp;lt;project root&amp;gt;/modules/vms&lt;/code&gt; directory. It will have two files, &lt;code&gt;main.tf&lt;/code&gt; and &lt;code&gt;variables.tf&lt;/code&gt;. The &lt;code&gt;main.tf&lt;/code&gt; will contain the code for creating VMs. The &lt;code&gt;variables.tf&lt;/code&gt; will have the variables for the module. The &lt;code&gt;main.tf&lt;/code&gt; will look like this:&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;"google_compute_instance"&lt;/span&gt; &lt;span class="s2"&gt;"virtual_machine"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;machine_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;machine_type&lt;/span&gt;
  &lt;span class="nx"&gt;zone&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&lt;/span&gt;

  &lt;span class="nx"&gt;scheduling&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;preemptible&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;automatic_restart&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="nx"&gt;provisioning_model&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SPOT"&lt;/span&gt;
    &lt;span class="nx"&gt;instance_termination_action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"STOP"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;allow_stopping_for_update&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allow_stopping_for_update&lt;/span&gt;

  &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"service_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;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sa_email&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sa_email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;email&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;service_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
      &lt;span class="nx"&gt;scopes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://www.googleapis.com/auth/cloud-platform"&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;boot_disk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;initialize_params&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"debian-cloud/debian-11"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;network_interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;network&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;
    &lt;span class="nx"&gt;subnetwork&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subnetwork&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In simple terms, look at how the code looks in each VPC for creating VMs. It is the same. You extract common code and put it in the module. All values that differ are replaced with variables. Terraform does not have a classic &lt;code&gt;if-then-else&lt;/code&gt; statement, so you must use &lt;a href="https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks"&gt;&lt;code&gt;dynamic&lt;/code&gt; block&lt;/a&gt;. So, this is my VM &lt;br&gt;
template.&lt;/p&gt;

&lt;p&gt;When using it in the &lt;code&gt;dev/services/vms&lt;/code&gt; module, it will look like this:&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;"services_vm_test"&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/vms"&lt;/span&gt;
  &lt;span class="nx"&gt;zone&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&lt;/span&gt;
  &lt;span class="nx"&gt;machine_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"f1-micro"&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;"services-vm-test"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services_id&lt;/span&gt;
  &lt;span class="nx"&gt;subnetwork&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services_subnetwork&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could even put &lt;code&gt;machine_type&lt;/code&gt; as a fixed value in the module. But I want to have the flexibility to change it. So, I'll leave it as a variable. For the &lt;code&gt;dev/back-office/vms&lt;/code&gt; module, it will look &lt;br&gt;
like this:&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;"vms"&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;toset&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"back-office-vm2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"back-office-private-vm1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"back-office-private-vm2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"back-office-vm1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&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/vms"&lt;/span&gt;
  &lt;span class="nx"&gt;zone&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&lt;/span&gt;
  &lt;span class="nx"&gt;machine_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"f1-micro"&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="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_back_office_id&lt;/span&gt;
  &lt;span class="nx"&gt;subnetwork&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_back_office_subnetwork&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;sa_email&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"back-office-vm1"&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office_fw_sa&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nx"&gt;allow_stopping_for_update&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"back-office-vm1"&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the machine name differs for each VM, I'm using the &lt;code&gt;for_each&lt;/code&gt; block. The only exception is &lt;code&gt;back-office-vm1&lt;/code&gt;. For this VM, I'm using a service account that I created for firewall rules. That's why I'm using the Elvis operator to check if the VM is &lt;code&gt;back-office-vm1&lt;/code&gt;. If it is, I'm using a service account; otherwise, I'm using an empty string.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reuse code in the peering module
&lt;/h3&gt;

&lt;p&gt;First, let's see the code:&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;peerings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"back-office-services-peering"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"network"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_back_office&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"peer_network"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"services-back-office-peering"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"network"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"peer_network"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_back_office&lt;/span&gt;&lt;span class="err"&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;"back-office-storage-peering"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"network"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_back_office&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"peer_network"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_storage&lt;/span&gt;&lt;span class="err"&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;"storage-back-office-peering"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"network"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_storage&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"peer_network"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_back_office&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_network_peering"&lt;/span&gt; &lt;span class="s2"&gt;"peerings"&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="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;peerings&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="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;
  &lt;span class="nx"&gt;peer_network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;peer_network&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, I define the &lt;code&gt;locals&lt;/code&gt; block. It contains a map of maps. Each map contains information about peering. In this case, I can not use variables because when you assign value to a variable in the &lt;code&gt;variable.tf&lt;/code&gt; file or &lt;code&gt;terraform.tfvars&lt;/code&gt; file, you can not use other variables. So, I have to use the &lt;code&gt;locals&lt;/code&gt; block. In the &lt;code&gt;locals&lt;/code&gt; block, I define a map for peering. The key is the peering name, and the value is a map with two keys: &lt;code&gt;network&lt;/code&gt; and &lt;code&gt;peer_network&lt;/code&gt;. This setup allows me to use the &lt;code&gt;for_each&lt;/code&gt; block in the &lt;code&gt;google_compute_network_peering&lt;/code&gt; resource.&lt;/p&gt;

&lt;p&gt;The final result is &lt;a href="https://github.com/robert-nemet/pcne/tree/add_modules"&gt;here&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;In this part, I refactored the project to make it more manageable: I split the project into environments, created a module for each VPC, and reused the code with modules. I used already existing modules, and I wrote my module. I used the &lt;code&gt;terraform state mv&lt;/code&gt; command to move resources from one state file to another. I used the &lt;code&gt;terraform state rm&lt;/code&gt; command to remove resources from the state file.&lt;/p&gt;

&lt;p&gt;In the next part, let's try to use &lt;a href="https://terragrunt.gruntwork.io/"&gt;Terragrunt&lt;/a&gt; to make our code even more DRY and make running CLI commands easier.&lt;/p&gt;

&lt;p&gt;Enjoy...&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>terraform</category>
      <category>gcp</category>
    </item>
    <item>
      <title>Exploring GCP With Terraform: VPC Firewall Rules, part 2</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Sat, 02 Sep 2023 09:05:12 +0000</pubDate>
      <link>https://forem.com/madmaxx/exploring-gcp-with-terraform-vpc-firewall-rules-part-2-mnj</link>
      <guid>https://forem.com/madmaxx/exploring-gcp-with-terraform-vpc-firewall-rules-part-2-mnj</guid>
      <description>&lt;p&gt;This post would be 3rd part of the series about exploring GCP with Terraform. In the &lt;a href="https://rnemet.dev/posts/gcp/gcp_tf_vpc/"&gt;previous part&lt;/a&gt;, I created VPC networks, subnets, and a few firewall rules. In this part, I will explore more firewall rules and their parameters.&lt;/p&gt;

&lt;p&gt;More precisely, I'll set up three VPCs: &lt;em&gt;back-office&lt;/em&gt;, &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;storage&lt;/em&gt;. In VPC &lt;em&gt;back_office&lt;/em&gt;, I'll have two subnets; in others, I'll have one subnet. For the sake of conversation, imagine that VMs in the &lt;em&gt;back-office&lt;/em&gt; have to call VMs in &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;storage&lt;/em&gt;. Also, direct access to  VMs from outside should not be allowed, except for one that will serve for maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I have done so far
&lt;/h2&gt;

&lt;p&gt;I have two VPCs: &lt;em&gt;back-office&lt;/em&gt; and &lt;em&gt;services&lt;/em&gt;. The VPC &lt;em&gt;back-office&lt;/em&gt; has a subnet &lt;em&gt;back-office&lt;/em&gt; and the VPC &lt;em&gt;services&lt;/em&gt; a subnet named &lt;em&gt;services&lt;/em&gt;. In each VPC, I added firewall rules to allow SSH access from outside through IAP. I also added a firewall rule to allow ICMP traffic from anywhere. Each VPC has a VM instance.&lt;/p&gt;

&lt;p&gt;The Terraform project is growing. If you look at how I set a &lt;code&gt;base&lt;/code&gt; workflow in &lt;a href="https://rnemet.dev/posts/gcp/start_with_gcp/#setting-up-the-project"&gt;the first post of this series&lt;/a&gt;, now I have two more similar workflows for &lt;code&gt;networks&lt;/code&gt; and &lt;code&gt;vms&lt;/code&gt;. It is not the best practice to have it like this, but this will be addressed later.&lt;/p&gt;

&lt;h2&gt;
  
  
  VPC Firewall rules
&lt;/h2&gt;

&lt;p&gt;So, what is a firewall rule? A firewall rule is a set of conditions that define what traffic is allowed to enter or leave a VPC network. A collection of parameters defines each firewall rule. Those parameters are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;direction - ingress or egress (default: ingress)&lt;/li&gt;
&lt;li&gt;priority - the priority of the rule. The lower the number, the higher the priority. The default value is 1000.&lt;/li&gt;
&lt;li&gt;action - allow or deny&lt;/li&gt;
&lt;li&gt;enforced - if the rule is enforced. The default value is true.&lt;/li&gt;
&lt;li&gt;target - the target of the rule. The target can be a tag, a service account, or a network.&lt;/li&gt;
&lt;li&gt;source - the source of the traffic. The source can be a tag, a service account, or a network.&lt;/li&gt;
&lt;li&gt;protocol - the protocol of the traffic. The protocols like TCP, UDP, ICMP, etc.&lt;/li&gt;
&lt;li&gt;logs - boolean. Make logs to see if the rule is matched or not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setting up the stage
&lt;/h3&gt;

&lt;p&gt;VPC &lt;em&gt;back_office&lt;/em&gt; has two subnets: &lt;em&gt;back-office&lt;/em&gt; and &lt;em&gt;back-office-private&lt;/em&gt;. So far, I have firewall rules: &lt;code&gt;back-office-iap&lt;/code&gt;, &lt;code&gt;back-office-icmp&lt;/code&gt;, and &lt;code&gt;back-office-ssh&lt;/code&gt;, allowing ingress traffic from Google IAP, ICMP from anywhere, and SSH from anywhere. So, adding a new subnet &lt;em&gt;back-office-private&lt;/em&gt; to the VPC &lt;em&gt;back-office&lt;/em&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="c1"&gt;# subnet for back office: private &lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_subnetwork"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_private"&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;"back-office-private"&lt;/span&gt;
  &lt;span class="nx"&gt;ip_cidr_range&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.2.0.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create a VM, you can use this code as a template inside the &lt;code&gt;vms&lt;/code&gt; workflow:&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;"google_compute_instance"&lt;/span&gt; &lt;span class="s2"&gt;"services_vm_test"&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;"services-vm-test"&lt;/span&gt;
  &lt;span class="nx"&gt;machine_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"f1-micro"&lt;/span&gt;
  &lt;span class="nx"&gt;zone&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&lt;/span&gt;

  &lt;span class="nx"&gt;scheduling&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;preemptible&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;automatic_restart&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="nx"&gt;provisioning_model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SPOT"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;boot_disk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;initialize_params&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"debian-cloud/debian-11"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;network_interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;network&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="nx"&gt;subnetwork&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services_subnetwork&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may wonder what is this part:&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;network_interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;network&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
    &lt;span class="nx"&gt;subnetwork&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_services_subnetwork&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where is that defined? Since I have a network creation in the different workflow &lt;code&gt;networks&lt;/code&gt;, I need to get the network ID and subnet ID from that workflow. I can do that by using the &lt;code&gt;terraform_remote_state&lt;/code&gt; data source. But first, I need to export it in the &lt;code&gt;networks&lt;/code&gt; workflow in the &lt;code&gt;outputs.tf&lt;/code&gt; 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;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_services"&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;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_services_subnetwork"&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;google_compute_subnetwork&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the template above for VM and exposing all VPCs and subnets from &lt;code&gt;networks&lt;/code&gt;, I can add two VMs in each subnet. The VMs in the &lt;em&gt;back-office&lt;/em&gt; subnet will be named &lt;em&gt;back-office-vm1&lt;/em&gt; and &lt;em&gt;back-office-vm2&lt;/em&gt;. The VMs in the &lt;em&gt;back-office-private&lt;/em&gt; subnet will be named &lt;em&gt;back-office-private-vm1&lt;/em&gt; and &lt;em&gt;back-office-private-vm2&lt;/em&gt;. First, I must change the &lt;code&gt;networks&lt;/code&gt; and then the &lt;code&gt;vms&lt;/code&gt;. Then, I can look for created instances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute instances list
NAME                     ZONE           MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP  STATUS
back-office-private-vm1  us-central1-c  f1-micro      &lt;span class="nb"&gt;true         &lt;/span&gt;10.2.0.3                  RUNNING
back-office-private-vm2  us-central1-c  f1-micro      &lt;span class="nb"&gt;true         &lt;/span&gt;10.2.0.2                  RUNNING
back-office-vm1          us-central1-c  f1-micro      &lt;span class="nb"&gt;true         &lt;/span&gt;10.1.0.9                  RUNNING
back-office-vm2          us-central1-c  f1-micro      &lt;span class="nb"&gt;true         &lt;/span&gt;10.1.0.10                 RUNNING
services-vm-test         us-central1-c  f1-micro      &lt;span class="nb"&gt;true         &lt;/span&gt;10.1.0.5                  RUNNING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, let's try to connect to any VM from the local machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute ssh back-office-vm1 &lt;span class="nt"&gt;--zone&lt;/span&gt; us-central1-c &lt;span class="nt"&gt;--tunnel-through-iap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, from it, let's try to connect to any other VM, ping it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 5 10.2.0.3
PING 10.2.0.3 &lt;span class="o"&gt;(&lt;/span&gt;10.2.0.3&lt;span class="o"&gt;)&lt;/span&gt; 56&lt;span class="o"&gt;(&lt;/span&gt;84&lt;span class="o"&gt;)&lt;/span&gt; bytes of data.
64 bytes from 10.2.0.3: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.884 ms
64 bytes from 10.2.0.3: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.165 ms
64 bytes from 10.2.0.3: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.233 ms
64 bytes from 10.2.0.3: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.159 ms
64 bytes from 10.2.0.3: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.238 ms

&lt;span class="nt"&gt;---&lt;/span&gt; 10.2.0.3 ping statistics &lt;span class="nt"&gt;---&lt;/span&gt;
5 packets transmitted, 5 received, 0% packet loss, &lt;span class="nb"&gt;time &lt;/span&gt;4103ms
rtt min/avg/max/mdev &lt;span class="o"&gt;=&lt;/span&gt; 0.159/0.335/0.884/0.276 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, you can connect to any VM inside VPC. Why? Because we have a firewall rule that allows ICMP traffic from anywhere. Let's delete that rule and try again(from the&lt;br&gt;
local machine):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute firewall-rules delete back-office-icmp
The following firewalls will be deleted:
 - &lt;span class="o"&gt;[&lt;/span&gt;back-office-icmp]

Do you want to &lt;span class="k"&gt;continue&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;Y/n&lt;span class="o"&gt;)&lt;/span&gt;?  y

Deleted &lt;span class="o"&gt;[&lt;/span&gt;https://www.googleapis.com/compute/v1/projects/network-playground-382512/global/firewalls/back-office-icmp].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat the ping command from the connected VM to any other VM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 5 10.2.0.3
PING 10.2.0.3 &lt;span class="o"&gt;(&lt;/span&gt;10.2.0.3&lt;span class="o"&gt;)&lt;/span&gt; 56&lt;span class="o"&gt;(&lt;/span&gt;84&lt;span class="o"&gt;)&lt;/span&gt; bytes of data.

&lt;span class="nt"&gt;---&lt;/span&gt; 10.2.0.3 ping statistics &lt;span class="nt"&gt;---&lt;/span&gt;
5 packets transmitted, 0 received, 100% packet loss, &lt;span class="nb"&gt;time &lt;/span&gt;4074ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, at this moment VMs, can not communicate with each other.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tightening access: Only one VM for SSH (bastion)
&lt;/h4&gt;

&lt;p&gt;Let's say we want to allow SSH access only to one VM from outside. We can do that by adding a firewall rule that allows SSH access only to one VM. Let's remove the rule &lt;em&gt;back-office-ssh&lt;/em&gt; as it is not practical. Why? I'm accessing VMs with the tunnel through IAP, so I do not need it. It would be helpful only if I could access them directly. That means that the target VM has an external IP. But I do not want that. &lt;/p&gt;

&lt;p&gt;And let's modify the rule &lt;em&gt;back-office-iap&lt;/em&gt; to allow SSH access only to one VM. Let's add the tag &lt;em&gt;bastion&lt;/em&gt; to the VM:&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;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_iap"&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;"back-office-iap"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;

  &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"35.235.240.0/20"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;target_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;"bastion"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS"&lt;/span&gt;
  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&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;I am using the tag &lt;em&gt;bastion&lt;/em&gt; to allow SSH access by applying this firewall rule only to VMs with particular tags. If I try to access the VM &lt;em&gt;back-office-vm1&lt;/em&gt; from the local machine, I will get an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute ssh back-office-vm1 &lt;span class="nt"&gt;--zone&lt;/span&gt; us-central1-c &lt;span class="nt"&gt;--tunnel-through-iap&lt;/span&gt;
ERROR: &lt;span class="o"&gt;(&lt;/span&gt;gcloud.compute.start-iap-tunnel&lt;span class="o"&gt;)&lt;/span&gt; Error &lt;span class="k"&gt;while &lt;/span&gt;connecting &lt;span class="o"&gt;[&lt;/span&gt;4003: &lt;span class="s1"&gt;'failed to connect to backend'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;Failed to connect to port 22&lt;span class="o"&gt;)&lt;/span&gt;
kex_exchange_identification: Connection closed by remote host
Connection closed by UNKNOWN port 65535

Recommendation: To check &lt;span class="k"&gt;for &lt;/span&gt;possible causes of SSH connectivity issues and get
recommendations, rerun the ssh &lt;span class="nb"&gt;command &lt;/span&gt;with the &lt;span class="nt"&gt;--troubleshoot&lt;/span&gt; option.

gcloud compute ssh back-office-vm1 &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-playground-382512 &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-c &lt;span class="nt"&gt;--troubleshoot&lt;/span&gt;

Or, to investigate an IAP tunneling issue:

gcloud compute ssh back-office-vm1 &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network-playground-382512 &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-c &lt;span class="nt"&gt;--troubleshoot&lt;/span&gt; &lt;span class="nt"&gt;--tunnel-through-iap&lt;/span&gt;

ERROR: &lt;span class="o"&gt;(&lt;/span&gt;gcloud.compute.ssh&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;/usr/bin/ssh] exited with &lt;span class="k"&gt;return &lt;/span&gt;code &lt;span class="o"&gt;[&lt;/span&gt;255].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add the tag &lt;em&gt;bastion&lt;/em&gt; to the VM &lt;em&gt;back-office-vm1&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute instances add-tags back-office-vm1 &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-c &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bastion
Updated &lt;span class="o"&gt;[&lt;/span&gt;https://www.googleapis.com/compute/v1/projects/network-playground-382512/zones/us-central1-c/instances/back-office-vm1].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, connecting to the VM &lt;em&gt;back-office-vm1&lt;/em&gt; works. But connecting to any other VM in the VPC &lt;em&gt;back-office&lt;/em&gt; doesn't work. Why? Because we have a firewall rule that allows SSH access only to the VM &lt;em&gt;back-office-vm1&lt;/em&gt;. The firewall matches the target with the tag value. If I tag any other VM in the VPC &lt;em&gt;back-office&lt;/em&gt; with the tag &lt;em&gt;bastion&lt;/em&gt;, I can connect to it.&lt;/p&gt;

&lt;p&gt;Well, I'm not happy that adding/removing tags is so easy. I want a more secure way to allow SSH access to VMs. I could use the service account to enable SSH access:&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;"google_service_account"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_fw_sa"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;account_id&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"back-office"&lt;/span&gt;
  &lt;span class="nx"&gt;display_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"back-office"&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;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_iap"&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;"back-office-iap"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;

  &lt;span class="nx"&gt;source_ranges&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"35.235.240.0/20"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;target_service_accounts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;google_service_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office_fw_sa&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS"&lt;/span&gt;
  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&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;google_service_account&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office_fw_sa&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;Connecting to the VM &lt;em&gt;back-office-vm1&lt;/em&gt; from the local machine, I will get the same error. Why? Because I'm not using the&lt;br&gt;
service account to connect to the VM. I'm using the tunnel through IAP. Let's add the service account to the VM &lt;em&gt;back-office-vm1&lt;/em&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_instance"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_vm1"&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;"back-office-vm1"&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;

  &lt;span class="nx"&gt;allow_stopping_for_update&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;service_account&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;email&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terraform_remote_state&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office_fw_sa&lt;/span&gt;
    &lt;span class="nx"&gt;scopes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://www.googleapis.com/auth/cloud-platform"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added the service account to the VM &lt;em&gt;back-office-vm1&lt;/em&gt;. Now, I can connect to it from the local machine. IAP can connect to it. Anyone who passes the IAP authentication can connect to it. But I can't connect to any other VM in the VPC &lt;em&gt;back-office&lt;/em&gt;. Why? Because I'm not using the service account to connect to the VMs. I'm using the tunnel through IAP, and the firewall rule allows access only to the &lt;em&gt;back-office-vm1&lt;/em&gt; VM.&lt;/p&gt;

&lt;p&gt;The scopes are required to allow the service account to access the resources. When adding service accounts to VMs, you must set &lt;code&gt;allow_stopping_for_update = true&lt;/code&gt; and service account scopes. Changing the service account on the VM requires stopping the VM.  &lt;/p&gt;

&lt;p&gt;This requirement makes a difference. You can add and remove tags on the fly but can't do that with service accounts. You must stop the VM to change the service account. That is why using service accounts is more secure than using tags.&lt;/p&gt;

&lt;p&gt;Notice that I exported the service account email from the &lt;code&gt;networks&lt;/code&gt; workflow. I did that because I needed to use it in the &lt;code&gt;vms&lt;/code&gt; workflow.&lt;/p&gt;

&lt;p&gt;OK, now I can connect to the VM &lt;em&gt;back-office-vm1&lt;/em&gt;, in the VPC &lt;em&gt;back-office&lt;/em&gt;, from the local machine via the IAP tunnel. But I can connect to any other VM in the VPC &lt;em&gt;back-office&lt;/em&gt;. But I still can connect to all other VMs in the VPC &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;storages&lt;/em&gt;. I'll remove the IAP firewall rule for those VPCs to fix this. But then I'll need access from the VPC's &lt;em&gt;back_office&lt;/em&gt; VMs to the VPC's &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;storages&lt;/em&gt; VMs. Keep the ICMP rule for VPC &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;storages&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;You can do it now if you did not add VPC &lt;em&gt;storages&lt;/em&gt;, a subnet, and VMs. Just remember to add VPC and subnet to the &lt;code&gt;networks&lt;/code&gt; workflow and VMs to the &lt;code&gt;vms&lt;/code&gt; workflow. For the &lt;em&gt;storage&lt;/em&gt; subnet in the VPC &lt;em&gt;storage&lt;/em&gt;, I used CIDR &lt;code&gt;10.120.0.0/24&lt;/code&gt;. And just for a reminder, CIDR for subnet &lt;em&gt;services&lt;/em&gt; in VPC &lt;em&gt;services&lt;/em&gt; is &lt;code&gt;10.1.0.0/24&lt;/code&gt;, and subnet &lt;em&gt;back-office&lt;/em&gt; in VPC &lt;em&gt;back_office&lt;/em&gt; is &lt;code&gt;10.1.0.0/24&lt;/code&gt; and subnet &lt;em&gt;back-office-private&lt;/em&gt; in VPC &lt;em&gt;back_office&lt;/em&gt; is &lt;code&gt;10.2.0.0/24&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tightening access: Accessing other VPCs
&lt;/h4&gt;

&lt;p&gt;OK, now what? I can't connect to any VM in the VPC &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;datastorages&lt;/em&gt; from the local machine because I just removed IAP firewall rules for those VPCs. I can connect only to &lt;em&gt;back-office_vm1&lt;/em&gt; from the local machine. But from it, I can connect to any other VM in the VPC &lt;em&gt;back-office&lt;/em&gt;, but not in other VPCs. To be able to connect from the local machine to &lt;em&gt;back-office-vm1&lt;/em&gt; and then to any VMs in VPC &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;storages&lt;/em&gt;, I need first to connect the VPC &lt;em&gt;back-office&lt;/em&gt;&lt;br&gt;
to VPC &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;storages&lt;/em&gt;. I can do that by setting up VPC peering. I'll set up VPC peering between VPC &lt;em&gt;back-office&lt;/em&gt; and &lt;em&gt;services&lt;/em&gt; and between VPC &lt;em&gt;back-office&lt;/em&gt; and &lt;em&gt;storages&lt;/em&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_network_peering"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_service_peering"&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;"back-office-services-peering"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
  &lt;span class="nx"&gt;peer_network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&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;"google_compute_network_peering"&lt;/span&gt; &lt;span class="s2"&gt;"service_back_office_peering"&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;"services-back-office-peering"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
  &lt;span class="nx"&gt;peer_network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&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;"google_compute_network_peering"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_datastorage_peering"&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;"back-office-storage-peering"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
  &lt;span class="nx"&gt;peer_network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&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;"google_compute_network_peering"&lt;/span&gt; &lt;span class="s2"&gt;"datastorage_back_office_peering"&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;"storage-back-office-peering"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
  &lt;span class="nx"&gt;peer_network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But we'll get an error when applying the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Error: Error waiting for Adding Network Peering: An IP range in the peer network (10.1.0.0/24) overlaps with an IP range in the local network (10.1.0.0/24) allocated by 
 resource (projects/network-playground-382512/regions/us-central1/subnetworks/back-office).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Because the VPC &lt;em&gt;back-office&lt;/em&gt; and &lt;em&gt;services&lt;/em&gt; have subnets with the same IP range. When setting up VPC peering, it is essential to know that the IP ranges of the subnets must not overlap. Changing the IP ranges of subnets in VPC &lt;em&gt;services&lt;/em&gt; is OK. But this means we need to plan the IP ranges of subnets. If you are using IPv6 in a VPC, then you don't need to worry about this. But, if you are using IPv4, you need to plan the IP ranges of subnets. The reason is that if you want to change CIDR for a subnet, you need to free all resources that use this subnet first, like deleting all VMs.&lt;/p&gt;

&lt;p&gt;Changing the IP ranges of subnets in VPC &lt;em&gt;services&lt;/em&gt; is easy. I'll change the subnet &lt;em&gt;services&lt;/em&gt; IP range to &lt;em&gt;10.3.0.0/24&lt;/em&gt;. But to do that, I need to delete the VMs in the VPC &lt;em&gt;services&lt;/em&gt; and recreate peering between VPCs. So, I'll remove VMs from the VPC &lt;em&gt;services&lt;/em&gt;&lt;em&gt;. Then, fixing the subnet range in VPC _services&lt;/em&gt;, recreate peering between VPCs. And finally, adding back deleted VMs to the VPC &lt;em&gt;services&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;While I'm doing this, I'll revert SSH access to the VMs in all VPCs, but this time, I'll use CIDR ranges to allow access:&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;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"services_ssh"&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;"services-ssh"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;

  &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.1.0.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.2.0.0/24"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS"&lt;/span&gt;
  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;ports&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"22"&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;This limits access to the VMs in the VPC &lt;em&gt;services&lt;/em&gt; from the VPC &lt;em&gt;back-office&lt;/em&gt; and &lt;em&gt;back-office-private&lt;/em&gt; subnets. I'll do the same for the VPC &lt;em&gt;storage&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You can add one for the VPC &lt;em&gt;storage&lt;/em&gt; as well. On top of all this, you'll need to add public SSH keys to the VMs. You can do it per VM or project. I'll do it on &lt;br&gt;
the project level for now:&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;"google_compute_project_metadata"&lt;/span&gt; &lt;span class="s2"&gt;"default"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ssh&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
      user:ssh-rsa public_key_goes_here user
&lt;/span&gt;&lt;span class="no"&gt;    EOF
&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;When adding SSH public keys to the project level, you are allowing SSH access to all VMs in the project. For now, this is good enough. To generate SSH keys, you can use the command &lt;code&gt;ssh-keygen&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh-keygen &lt;span class="nt"&gt;-f&lt;/span&gt; private-key &lt;span class="nt"&gt;-C&lt;/span&gt; rnemet &lt;span class="nt"&gt;-b&lt;/span&gt; 2048
Generating public/private rsa key pair.
Enter passphrase &lt;span class="o"&gt;(&lt;/span&gt;empty &lt;span class="k"&gt;for &lt;/span&gt;no passphrase&lt;span class="o"&gt;)&lt;/span&gt;:
Enter same passphrase again:
Your identification has been saved &lt;span class="k"&gt;in &lt;/span&gt;private-key
Your public key has been saved &lt;span class="k"&gt;in &lt;/span&gt;private-key.pub
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above will generate two files: &lt;em&gt;private-key&lt;/em&gt; and &lt;em&gt;private-key.pub&lt;/em&gt;. The file &lt;em&gt;private-key&lt;/em&gt; is the private key, and the file &lt;em&gt;private-key.pub&lt;/em&gt; is the public key. Now, you&lt;br&gt;
can use the public key to add it to the project metadata. You can also use the private key to connect to the VMs. That means you must keep the private key safe and be on machines you use to connect to the VMs. After adding the public key to the project metadata, you can connect to the VMs in the VPC &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;storages&lt;/em&gt; from VPC &lt;em&gt;back-office&lt;/em&gt; VMs. All you need when connecting to the VMs is the private key on bastion VMs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute scp ./private-key rnemet@back-office-vm1:.ssh/private-key &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-c
External IP address was not found&lt;span class="p"&gt;;&lt;/span&gt; defaulting to using IAP tunneling.
WARNING:

To increase the performance of the tunnel, consider installing NumPy. For instructions,
please see https://cloud.google.com/iap/docs/using-tcp-forwarding#increasing_the_tcp_upload_bandwidth

private-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When connecting to the VMs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh rnemet@10.120.0.3 &lt;span class="nt"&gt;-i&lt;/span&gt; .ssh/private-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, I can connect to the VMs in the VPC &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;storages&lt;/em&gt; from the VM in the VPC &lt;em&gt;back-office&lt;/em&gt;. Direct access to other VMs is not possible because&lt;br&gt;
of IAP and firewall rules.&lt;/p&gt;

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

&lt;p&gt;So far, I have created VPCs, subnets, and VMs. I have also created firewall rules to allow SSH access to the VMs from one VM inside my networks. I have also created VPC peering between VPCs. Also, I started to share resources between Terraform state files. However, my TF project is growing and needs a better structure. I need to invest some time to improve it. But at the same time, I would like to put some services that are reachable from the outside.&lt;/p&gt;

&lt;p&gt;What do you think? What would you do next? Let me know...&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/firewall/docs/firewalls"&gt;GCP Firewall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_firewall"&gt;GCP TF firewall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_instance"&gt;GCP TF instances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/sdk/gcloud/reference/alpha/compute/instances/set-scopes"&gt;GCP scopes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vpc/docs/vpc-peering"&gt;GCP VPC peering&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>tutorial</category>
      <category>gcp</category>
      <category>devops</category>
    </item>
    <item>
      <title>Exploring GCP With Terraform: VPCs, Firewall Rules And VMs</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Wed, 23 Aug 2023 14:01:56 +0000</pubDate>
      <link>https://forem.com/madmaxx/exploring-gcp-with-terraform-vpcs-firewall-rules-and-vms-53f7</link>
      <guid>https://forem.com/madmaxx/exploring-gcp-with-terraform-vpcs-firewall-rules-and-vms-53f7</guid>
      <description>&lt;p&gt;This post will continue my previous post &lt;a href="https://rnemet.dev/posts/gcp/start_with_gcp/"&gt;Exploring GCP With Terraform: Setting Up The Environment And Project&lt;/a&gt;.&lt;br&gt;
In this post, I'll:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create VPC with Terraform&lt;/li&gt;
&lt;li&gt;Create subnets/firewall rules&lt;/li&gt;
&lt;li&gt;Create VMs in the VPC&lt;/li&gt;
&lt;li&gt;How to access to VMs from outside and inside the VPC&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
  Some Info Here
  &lt;br&gt;
Also, I'll mention how to use &lt;code&gt;gcloud&lt;/code&gt; to fetch information about created resources. At the end, I'll mention two useful Terraform CLI commands. And why I'm structuring the project as I do.

&lt;p&gt;This structuring is one of many ways to do it. It is just my way of doing it for now. The project needs refactoring, but for now, it is good enough.&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;
  Some Info Here
  &lt;br&gt;
I replaced the real project ID with _project-id_in the following examples. You need to replace it with your project ID.&lt;br&gt;


&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://rnemet.substack.com/" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--ngDK5v8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://substackcdn.com/image/fetch/f_auto%2Cq_auto:best%2Cfl_progressive:steep/https%253A%252F%252Frnemet.substack.com%252Ftwitter%252Fsubscribe-card.jpg%253Fv%253D-1774894496%2526version%253D9" height="417" class="m-0" width="800"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://rnemet.substack.com/" rel="noopener noreferrer" class="c-link"&gt;
          DevCube | Robert Nemet | Substack
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Weekly rant about software design, devops, kubernetes, sre... Click to read DevCube, by Robert Nemet, a Substack publication. Launched 6 months ago.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--hjtaVPaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://substackcdn.com/image/fetch/f_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fsubstack-post-media.s3.amazonaws.com%252Fpublic%252Fimages%252F62a252ac-a23f-428d-8013-8216e8ad1baa%252Ffavicon.ico" width="64" height="64"&gt;
        rnemet.substack.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  What is VPC in GCP?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Virtual Private Network&lt;/strong&gt;(VPC) is a virtual network defined in the cloud. It is a private network isolated from other networks in the cloud. It is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connectivity between VMs&lt;/li&gt;
&lt;li&gt;Traffic distribution from GC load balancers to backends,&lt;/li&gt;
&lt;li&gt;Connecting on-premises networks with Cloud VPN or Cloud Interconnect,&lt;/li&gt;
&lt;li&gt;Network Load Balancers and proxies for internal Application Load Balancers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;VPC is a global resource in GCP. A global resource means it is not bound to any region or zone. It is a logical resource that spans all regions.&lt;/p&gt;

&lt;p&gt;
  Some Info Here
  &lt;br&gt;
We can distinguish between two types of resources: &lt;em&gt;global and regional&lt;/em&gt;. Global resources are not bound to any region or zone. They are logical resources that span all regions.

&lt;p&gt;Regional resources are bound to a specific region. They are physical resources that exist in a specific region. Each region has at least three zones. Zones are isolated from each other and independent of each other. If one zone fails, other zones are not affected. You can perceive a zone as a data center. They are connected to a high-bandwidth, low-latency network.&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;You can imagine VPC as a logical container that holds all the other networking resources like firewalls, routes, subnets, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subnets
&lt;/h3&gt;

&lt;p&gt;Subnets are logical partitions of the VPC's IP address range. They are regional resources, which means they are bound to a specific region. You can specify the IP address range as IPv4 (single stack) or IPv4/IPv6 (dual-stack). They are allocating IP addresses to other resources in the subnet's region.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firewall Rules
&lt;/h3&gt;

&lt;p&gt;Firewall rules are used to control traffic to and from different destinations. They are applied to the VPC network and are enforced at the VM instance level. Every VPC&lt;br&gt;
network has two implied firewall rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;implied allow egress&lt;/em&gt;. Egress(outgoing) traffic is allowed to all destinations.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;implied deny ingress&lt;/em&gt;. Ingress(incoming) traffic is denied from all sources.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As well as the implied rules, you can create your own rules.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating VPC with Terraform
&lt;/h2&gt;

&lt;p&gt;I set up a base for my Terraform project in a &lt;a href="https://rnemet.dev/posts/gcp/start_with_gcp/"&gt;previous post&lt;/a&gt;. Now, next to the &lt;code&gt;base&lt;/code&gt; directory, I'm creating a new &lt;code&gt;network&lt;/code&gt; directory &lt;br&gt;
with the same file structure. Let's start with the &lt;code&gt;provider.tf&lt;/code&gt; 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;provider&lt;/span&gt; &lt;span class="s2"&gt;"google"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;zone&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;=1.5.5"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google"&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;"4.77.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"gcs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-states-project-id"&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform/state/network"&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;Provider for all workflows is the same as in the &lt;code&gt;base&lt;/code&gt; directory. The difference is the &lt;code&gt;backend.gcs.prefix&lt;/code&gt;, which is now &lt;code&gt;terraform/state/network&lt;/code&gt;. I'm using the same bucket. For the &lt;code&gt;base&lt;/code&gt; workflow, you can change it to &lt;code&gt;terraform/state/base&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;
  Some Info Here
  &lt;p&gt;I'm splitting state files by workflows. The reason is to minimize the impact of changes from one workflow to another. There will be a dependency between workflows, but it should be minimal. Splitting state files would be necessary if multiple teams worked on the same project.&lt;/p&gt;



&lt;/p&gt;

&lt;p&gt;And now creating the VPC in &lt;code&gt;main.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="c1"&gt;# vpc: back office&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_network"&lt;/span&gt; &lt;span class="s2"&gt;"back_office"&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;"back-office"&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;"Back office network"&lt;/span&gt;
  &lt;span class="nx"&gt;auto_create_subnetworks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;routing_mode&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"REGIONAL"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I'm creating a VPC named &lt;code&gt;back-office&lt;/code&gt; and turning off the auto-creation of subnets. It is recommended to set &lt;code&gt;auto_create_subnetworks&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; and create subnets &lt;br&gt;
manually. This way, you have more control over the subnets. Otherwise, GCP will create a subnet in each region.&lt;/p&gt;

&lt;p&gt;
  Some Info Here
  &lt;p&gt;You can list all VPCs in a project with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;$ &lt;/span&gt;gcloud compute networks list

NAME         SUBNET_MODE  BGP_ROUTING_MODE  IPV4_RANGE  GATEWAY_IPV4
back-office  CUSTOM       REGIONAL
default      AUTO         REGIONAL
services     CUSTOM       REGIONAL
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Notice a &lt;em&gt;default&lt;/em&gt; VPC with &lt;em&gt;AUTO&lt;/em&gt; subnet mode. The &lt;code&gt;default&lt;/code&gt; VPC is created when you make a project. It has a subnet in each region. I created other VPCs.&lt;/p&gt;

&lt;p&gt;To get info on a specific VPC, try:&lt;br&gt;
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;$ &lt;/span&gt;gcloud compute networks describe back-office

autoCreateSubnetworks: &lt;span&gt;false
&lt;/span&gt;creationTimestamp: &lt;span&gt;'2023-08-20T07:06:11.543-07:00'&lt;/span&gt;
description: Back office network
&lt;span&gt;id&lt;/span&gt;: &lt;span&gt;'1334556407227679868'&lt;/span&gt;
kind: compute#network
name: back-office
networkFirewallPolicyEnforcementOrder: AFTER_CLASSIC_FIREWALL
routingConfig:
  routingMode: REGIONAL
selfLink: https://www.googleapis.com/compute/v1/projects/project-id/global/networks/back-office
selfLinkWithId: https://www.googleapis.com/compute/v1/projects/project-id/global/networks/1334556407227679868
subnetworks:
- https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/back-office
x_gcloud_bgp_routing_mode: REGIONAL
x_gcloud_subnet_mode: CUSTOM
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;

&lt;p&gt;I am setting the routing mode to &lt;code&gt;REGIONAL&lt;/code&gt;. The &lt;code&gt;routing_mode&lt;/code&gt; can be set to &lt;code&gt;REGIONAL&lt;/code&gt; or &lt;code&gt;GLOBAL&lt;/code&gt;. The &lt;code&gt;REGIONAL&lt;/code&gt; means advertising routes to subnets in the same &lt;br&gt;
region. The &lt;code&gt;GLOBAL&lt;/code&gt; means advertising routes to subnets in all regions.&lt;/p&gt;

&lt;p&gt;As mentioned, more is needed. I need to add a subnet to the VPC:&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;# subnet for back office&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_compute_subnetwork"&lt;/span&gt; &lt;span class="s2"&gt;"back_office"&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;"back-office"&lt;/span&gt;
  &lt;span class="nx"&gt;ip_cidr_range&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.1.0.0/24"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I'm creating a subnet named &lt;code&gt;back-office&lt;/code&gt; and IP range &lt;code&gt;10.1.0.0/24&lt;/code&gt;. This subnet is attached to the &lt;code&gt;back_office&lt;/code&gt; VPC in a predefined region.&lt;/p&gt;

&lt;p&gt;
  Some Info Here
  &lt;p&gt;The &lt;em&gt;self_link&lt;/em&gt; refers to the resource and is a unique identifier for the resource. It is used to reference the resource in other resources.&lt;/p&gt;

&lt;p&gt;The IP range is expressed as CIDR notation. The CIDR notation is a compact representation of an IP address and its associated routing prefix. The prefix is written after the IP address, and the prefix length is indicated by the number of bits set to 1 in the subnet mask. Check resources for more info.&lt;br&gt;
&lt;/p&gt;

&lt;/p&gt;

&lt;p&gt;So, I should have a VPC with a subnet. You can check the details of this subnet with &lt;code&gt;gcloud&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute networks subnets describe back-office &lt;span class="nt"&gt;--region&lt;/span&gt; us-central1

creationTimestamp: &lt;span class="s1"&gt;'2023-08-21T13:12:57.423-07:00'&lt;/span&gt;
enableFlowLogs: &lt;span class="nb"&gt;false
&lt;/span&gt;fingerprint: &lt;span class="nv"&gt;crXvadZ1JQ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
gatewayAddress: 10.1.0.1
&lt;span class="nb"&gt;id&lt;/span&gt;: &lt;span class="s1"&gt;'88832453211859900582'&lt;/span&gt;
ipCidrRange: 10.1.0.0/24
kind: compute#subnetwork
logConfig:
  aggregationInterval: INTERVAL_5_SEC
  &lt;span class="nb"&gt;enable&lt;/span&gt;: &lt;span class="nb"&gt;false
  &lt;/span&gt;flowSampling: 0.5
  metadata: INCLUDE_ALL_METADATA
name: back-office
network: https://www.googleapis.com/compute/v1/projects/project-id/global/networks/back-office
privateIpGoogleAccess: &lt;span class="nb"&gt;false
&lt;/span&gt;privateIpv6GoogleAccess: DISABLE_GOOGLE_ACCESS
purpose: PRIVATE
region: https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1
selfLink: https://www.googleapis.com/compute/v1/projects/project-id/regions/us-central1/subnetworks/back-office
stackType: IPV4_ONLY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Add a VM
&lt;/h3&gt;

&lt;p&gt;Now is the time to test this. I'm creating a VM in this subnet:&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;"google_compute_instance"&lt;/span&gt; &lt;span class="s2"&gt;"bo_vm_test_alpha"&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;"bo-vm-test-alpha"&lt;/span&gt;
  &lt;span class="nx"&gt;machine_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"f1-micro"&lt;/span&gt;
  &lt;span class="nx"&gt;zone&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&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;"bo-vm-test"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;scheduling&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;preemptible&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;automatic_restart&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="nx"&gt;provisioning_model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SPOT"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;boot_disk&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;initialize_params&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"debian-cloud/debian-11"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;network_interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;network&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;
    &lt;span class="nx"&gt;subnetwork&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_subnetwork&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&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;I'm creating a VM named &lt;code&gt;bo-vm-test-alpha&lt;/code&gt; and machine type &lt;code&gt;f1-micro&lt;/code&gt;. The VM is made in the zone &lt;code&gt;us-central1-a&lt;/code&gt;. The VM is tagged with the &lt;code&gt;bo-vm-test&lt;/code&gt; tag. The VM is preemptible, and it is using a spot instance. The boot disk is using the Debian 11 image. The VM is attached to the &lt;code&gt;back_office&lt;/code&gt; VPC and &lt;code&gt;back-office&lt;/code&gt; subnet.&lt;/p&gt;

&lt;p&gt;This VM would be the cheapest in GCP, and it is not suitable for production. It is just for testing purposes. If you want to access it, go to the &lt;a href="https://console.cloud.google.com/compute/instances"&gt;Console&lt;/a&gt;. There, choose the right project, and you should see your VM. Then go to the &lt;em&gt;SSH&lt;/em&gt; button and click on it. It will open a new window with a terminal. You should be able to access the VM. Or not.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add Firewall Rules
&lt;/h3&gt;

&lt;p&gt;You can't. Why? Because I didn't create a firewall rule to allow SSH:&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;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_ssh"&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;"back-office-ssh"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;

  &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS"&lt;/span&gt;
  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;ports&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"22"&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;I'm creating a firewall rule named &lt;code&gt;back-office-ssh&lt;/code&gt;, applied to the &lt;code&gt;back_office&lt;/code&gt; VPC. The source range is &lt;code&gt;0.0.0.0/0&lt;/code&gt;, which means all internet. The direction is &lt;code&gt;INGRESS&lt;/code&gt;, allowing TCP traffic on port &lt;code&gt;22&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This time, let's try to connect with &lt;code&gt;gcloud&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute ssh bo-vm-test-alpha &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-c  &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project-id
External IP address was not found&lt;span class="p"&gt;;&lt;/span&gt; defaulting to using IAP tunneling.
WARNING:

To increase the performance of the tunnel, consider installing NumPy. For instructions,
please see https://cloud.google.com/iap/docs/using-tcp-forwarding#increasing_the_tcp_upload_bandwidth

ERROR: &lt;span class="o"&gt;(&lt;/span&gt;gcloud.compute.start-iap-tunnel&lt;span class="o"&gt;)&lt;/span&gt; Error &lt;span class="k"&gt;while &lt;/span&gt;connecting &lt;span class="o"&gt;[&lt;/span&gt;4003: &lt;span class="s1"&gt;'failed to connect to backend'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;Failed to connect to port 22&lt;span class="o"&gt;)&lt;/span&gt;
kex_exchange_identification: Connection closed by remote host
Connection closed by UNKNOWN port 65535

Recommendation: To check &lt;span class="k"&gt;for &lt;/span&gt;possible causes of SSH connectivity issues and get
recommendations, rerun the ssh &lt;span class="nb"&gt;command &lt;/span&gt;with the &lt;span class="nt"&gt;--troubleshoot&lt;/span&gt; option.

gcloud compute ssh bo-vm-test-alpha &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project-id &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-c &lt;span class="nt"&gt;--troubleshoot&lt;/span&gt;

Or, to investigate an IAP tunneling issue:

gcloud compute ssh bo-vm-test-alpha &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project-id &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-c &lt;span class="nt"&gt;--troubleshoot&lt;/span&gt; &lt;span class="nt"&gt;--tunnel-through-iap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It is not working. I have two options to fix this: to add an external IP address to the VM or to use IAP tunneling. I'll go with the second option. IAP tunneling is used to connect to VMs without external IP addresses. The VM I just created does not have an external IP address. So, let's enable IAP tunneling:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;iap.googleapis.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And add a firewall rule to allow ingress from the Cloud IAP for TCP forwarding:&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;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_iap"&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;"back-office-iap"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;

  &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"35.235.240.0/20"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS"&lt;/span&gt;
  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The CIDR range &lt;code&gt;35.235.240.0/20&lt;/code&gt; is the range used by IAP. GCP specifies it &lt;a href="https://cloud.google.com/iap/docs/using-tcp-forwarding#create-firewall-rule"&gt;see here for more info&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;terraform apply&lt;/code&gt;. Please wait for it to finish and try again. This time, it should work:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; gcloud compute ssh bo-vm-test-alpha &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-c  &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project-id
External IP address was not found&lt;span class="p"&gt;;&lt;/span&gt; defaulting to using IAP tunneling.
WARNING:

To increase the performance of the tunnel, consider installing NumPy. For instructions,
please see https://cloud.google.com/iap/docs/using-tcp-forwarding#increasing_the_tcp_upload_bandwidth

Warning: Permanently added &lt;span class="s1"&gt;'compute.89653456855762463'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ED25519&lt;span class="o"&gt;)&lt;/span&gt; to the list of known hosts.
Linux bo-vm-test-alpha 5.10.0-24-cloud-amd64 &lt;span class="c"&gt;#1 SMP Debian 5.10.179-5 (2023-08-08) x86_64&lt;/span&gt;

The programs included with the Debian GNU/Linux system are free software&lt;span class="p"&gt;;&lt;/span&gt;
the exact distribution terms &lt;span class="k"&gt;for &lt;/span&gt;each program are described &lt;span class="k"&gt;in &lt;/span&gt;the
individual files &lt;span class="k"&gt;in&lt;/span&gt; /usr/share/doc/&lt;span class="k"&gt;*&lt;/span&gt;/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
rnemet@bo-vm-test-alpha:~&lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It is working. I connect to the VM. Now, I can connect to the VM from another VM in the same subnet. I'll create another VM in the same subnet. Just name it &lt;code&gt;bo-vm-test-beta&lt;/code&gt;. When done, check it with &lt;code&gt;gcloud&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute instances list
NAME              ZONE           MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP  STATUS
bo-vm-test-alpha  us-central1-c  f1-micro      &lt;span class="nb"&gt;true         &lt;/span&gt;10.1.0.5                  RUNNING
bo-vm-test-beta   us-central1-c  f1-micro      &lt;span class="nb"&gt;true         &lt;/span&gt;10.1.0.6                  RUNNING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I see that &lt;em&gt;alpha&lt;/em&gt; has IP 10.1.0.5 and &lt;em&gt;beta&lt;/em&gt; has IP 10.1.0.6. Now I'll try to &lt;code&gt;ping&lt;/code&gt; &lt;em&gt;alpha&lt;/em&gt; from &lt;em&gt;beta&lt;/em&gt;. But first, I need to allow ICMP traffic:&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;"google_compute_firewall"&lt;/span&gt; &lt;span class="s2"&gt;"back_office_icmp"&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;"back-office-icmp"&lt;/span&gt;
  &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_compute_network&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;back_office&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_link&lt;/span&gt;

  &lt;span class="nx"&gt;source_ranges&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INGRESS"&lt;/span&gt;
  &lt;span class="nx"&gt;allow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"icmp"&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;And then let's try to ping &lt;em&gt;alpha&lt;/em&gt; from the &lt;em&gt;beta&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud compute ssh bo-vm-test-beta &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-central1-c  &lt;span class="nt"&gt;--project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project-id
External IP address was not found&lt;span class="p"&gt;;&lt;/span&gt; defaulting to using IAP tunneling.
WARNING:

To increase the performance of the tunnel, consider installing NumPy. For instructions,
please see https://cloud.google.com/iap/docs/using-tcp-forwarding#increasing_the_tcp_upload_bandwidth

Linux bo-vm-test-beta 5.10.0-24-cloud-amd64 &lt;span class="c"&gt;#1 SMP Debian 5.10.179-5 (2023-08-08) x86_64&lt;/span&gt;

The programs included with the Debian GNU/Linux system are free software&lt;span class="p"&gt;;&lt;/span&gt;
the exact distribution terms &lt;span class="k"&gt;for &lt;/span&gt;each program are described &lt;span class="k"&gt;in &lt;/span&gt;the
individual files &lt;span class="k"&gt;in&lt;/span&gt; /usr/share/doc/&lt;span class="k"&gt;*&lt;/span&gt;/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Aug 22 21:13:44 2023 from 35.235.244.34
rnemet@bo-vm-test-beta:~&lt;span class="nv"&gt;$ &lt;/span&gt;ping 10.1.0.6
PING 10.1.0.6 &lt;span class="o"&gt;(&lt;/span&gt;10.1.0.6&lt;span class="o"&gt;)&lt;/span&gt; 56&lt;span class="o"&gt;(&lt;/span&gt;84&lt;span class="o"&gt;)&lt;/span&gt; bytes of data.
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.014 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.045 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.035 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.037 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.029 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.036 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.022 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.035 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.028 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.036 ms
64 bytes from 10.1.0.6: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;11 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.036 ms
^C
&lt;span class="nt"&gt;---&lt;/span&gt; 10.1.0.6 ping statistics &lt;span class="nt"&gt;---&lt;/span&gt;
11 packets transmitted, 11 received, 0% packet loss, &lt;span class="nb"&gt;time &lt;/span&gt;10232ms
rtt min/avg/max/mdev &lt;span class="o"&gt;=&lt;/span&gt; 0.014/0.032/0.045/0.008 ms
rnemet@bo-vm-test-beta:~&lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;
  Some Info Here
  &lt;br&gt;
You'll need to wait a few minutes for the firewall rule to take effect. Or restart the VM:&lt;br&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;$ &lt;/span&gt;gcloud compute instances reset bo-vm-test-beta bo-vm-test-alpha &lt;span&gt;--zone&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;us-central1-c
Updated &lt;span&gt;[&lt;/span&gt;https://www.googleapis.com/compute/v1/projects/project-id/zones/us-central1-c/instances/bo-vm-test-beta].
Updated &lt;span&gt;[&lt;/span&gt;https://www.googleapis.com/compute/v1/projects/project-id/zones/us-central1-c/instances/bo-vm-test-alpha].
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;
&lt;h2&gt;
  
  
  Terraform CLI: fmt and validate
&lt;/h2&gt;

&lt;p&gt;Frequent code changes make a mess. It is good practice to keep the code clean and formatted. Terraform has a command to format the code: &lt;code&gt;terraform fmt&lt;/code&gt;. It will format the code in the current directory. It will also format all the files in the subdirectories. It is a good practice to run this command before committing the code.&lt;/p&gt;

&lt;p&gt;On the other side, &lt;code&gt;terraform validate&lt;/code&gt; will check the syntax of the code. It will check if the code is valid and complete. It will not check if the code is correct. For example, if you have a typo in the resource name, it will not catch it. It will detect if you have a typo in the resource type. It will also check if all the variables are defined.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, I created a VPC with a subnet and a VM in the subnet. I also made firewall rules to allow SSH and ICMP traffic. I used &lt;code&gt;gcloud&lt;/code&gt; to check the created resources. I also used &lt;code&gt;gcloud&lt;/code&gt; to connect to the VMs. I used IAP tunneling to connect to the VM without an external IP address. I also used &lt;code&gt;terraform fmt&lt;/code&gt; and &lt;code&gt;terraform validate&lt;/code&gt; to format and validate the code.&lt;/p&gt;

&lt;p&gt;I'll create a second VPC and a VM for the next post. Then, explore how to connect VMs from different VPCs. As well as how to route traffic between VPCs.&lt;/p&gt;

&lt;p&gt;If you find this helpful, please share it with others. If you have any questions or comments, please let me know in newsletter comments or via email.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://rnemet.substack.com/embed" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--ngDK5v8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://substackcdn.com/image/fetch/f_auto%2Cq_auto:best%2Cfl_progressive:steep/https%253A%252F%252Frnemet.substack.com%252Ftwitter%252Fsubscribe-card.jpg%253Fv%253D-1774894496%2526version%253D9" height="417" class="m-0" width="800"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://rnemet.substack.com/embed" rel="noopener noreferrer" class="c-link"&gt;
          DevCube | Robert Nemet | Substack
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Weekly rant about software design, devops, kubernetes, sre... Click to read DevCube, by Robert Nemet, a Substack publication. Launched 6 months ago.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--hjtaVPaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://substackcdn.com/image/fetch/f_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fsubstack-post-media.s3.amazonaws.com%252Fpublic%252Fimages%252F62a252ac-a23f-428d-8013-8216e8ad1baa%252Ffavicon.ico" width="64" height="64"&gt;
        rnemet.substack.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/compute/docs/regions-zones"&gt;Regions and Zones&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vpc/docs/overview"&gt;VPC Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vpc/docs/subnets"&gt;VPCs and Subnets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vpc/docs/firewalls"&gt;Firewall Rules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing"&gt;CIDR Notation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/iap/docs/using-tcp-forwarding"&gt;GCP IAP Tcp forwarding&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>terraform</category>
      <category>gcp</category>
    </item>
    <item>
      <title>Exploring GCP With Terraform: Setting Up The Environment And Project</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Sat, 19 Aug 2023 19:02:21 +0000</pubDate>
      <link>https://forem.com/madmaxx/exploring-gcp-with-terraform-setting-up-the-environment-and-project-58h7</link>
      <guid>https://forem.com/madmaxx/exploring-gcp-with-terraform-setting-up-the-environment-and-project-58h7</guid>
      <description>&lt;p&gt;This topic is nothing new. There are many articles and tutorials on the internet about this. I'll be setting up a Terraform project to explore GCP. I cover the basics of Terraform and GCP.&lt;/p&gt;

&lt;p&gt;
  Read Before Proceed..
  &lt;br&gt;
I replaced the real project ID with &lt;code&gt;project-id&lt;/code&gt; in the following examples. You need to replace it with your project ID.&lt;br&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create GCP Project
&lt;/h3&gt;

&lt;p&gt;First, you must create a GCP account, then create a project and set up billing on that project. If you already have a Gmail account, you can use it to make a GCP account. Head to the &lt;a href="https://console.cloud.google.com/"&gt;Google Cloud Console&lt;/a&gt; and create a new project.&lt;/p&gt;

&lt;p&gt;My plan is to pay for the services I use. I need to feel the pain and learn more about cost optimization. You can be more intelligent than me and use the free tier for the first year. After that, you will be charged for the services you use. You can find more information about the &lt;a href="https://cloud.google.com/free/docs/gcp-free-tier"&gt;free tier here&lt;/a&gt;.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://rnemet.substack.com/embed" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--ngDK5v8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://substackcdn.com/image/fetch/f_auto%2Cq_auto:best%2Cfl_progressive:steep/https%253A%252F%252Frnemet.substack.com%252Ftwitter%252Fsubscribe-card.jpg%253Fv%253D-1774894496%2526version%253D9" height="417" class="m-0" width="800"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://rnemet.substack.com/embed" rel="noopener noreferrer" class="c-link"&gt;
          DevCube | Robert Nemet | Substack
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Weekly rant about software design, devops, kubernetes, sre... Click to read DevCube, by Robert Nemet, a Substack publication. Launched 6 months ago.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--hjtaVPaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://substackcdn.com/image/fetch/f_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fsubstack-post-media.s3.amazonaws.com%252Fpublic%252Fimages%252F62a252ac-a23f-428d-8013-8216e8ad1baa%252Ffavicon.ico" width="64" height="64"&gt;
        rnemet.substack.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Install And Set Up The gcloud CLI Tool
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;gcloud&lt;/code&gt; CLI is a part of the Google Cloud SDK. You can find the installation instructions for &lt;a href="https://cloud.google.com/sdk/docs/install"&gt;&lt;code&gt;gcloud&lt;/code&gt; CLI here&lt;/a&gt;. Next, you need to set up the &lt;code&gt;gcloud&lt;/code&gt; CLI tool. Docs are &lt;a href="https://cloud.google.com/sdk/docs/initializing"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For future reference, you can find the list of all &lt;code&gt;gcloud&lt;/code&gt; commands &lt;a href="https://cloud.google.com/sdk/gcloud/reference"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After initialization, you can always check what you set with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud config list &lt;span class="o"&gt;[&lt;/span&gt;SECTION/PROPERTY] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;--all&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Install Terraform CLI Tool
&lt;/h3&gt;

&lt;p&gt;You can find the Terraform installation instructions &lt;a href="https://learn.hashicorp.com/tutorials/terraform/install-cli"&gt;here&lt;/a&gt;. But I'm not following the instructions.&lt;/p&gt;

&lt;p&gt;I'm using the tool &lt;a href="https://github.com/tfutils/tfenv"&gt;tfenv&lt;/a&gt; to manage Terraform versions. Other tools can do that. You can use &lt;a href="https://asdf-vm.com/"&gt;&lt;code&gt;asdf&lt;/code&gt;&lt;/a&gt;, too. I saw that &lt;code&gt;asdf&lt;/code&gt; can do more than manage Terraform versions.&lt;/p&gt;

&lt;p&gt;Let me explain how I do it since I use &lt;code&gt;tfenv&lt;/code&gt;. I assume you already installed it. I use &lt;code&gt;tfenv&lt;/code&gt; to install the desired version of Terraform. And to set it as default:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tfenv &lt;span class="nb"&gt;install &lt;/span&gt;1.5.5
&lt;span class="nv"&gt;$ &lt;/span&gt;tfenv use 1.5.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can check the version with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I'll explain later why I use &lt;code&gt;tfenv&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Other Tools I Use
&lt;/h3&gt;

&lt;p&gt;Other tools I'll be using are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://taskfile.dev/"&gt;task&lt;/a&gt; - a task runner and a replacement for &lt;code&gt;make&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/"&gt;git&lt;/a&gt; - version control system&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://direnv.net/"&gt;direnv&lt;/a&gt; - unconsciously set and unset environment variables&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Setting Up The Project
&lt;/h2&gt;

&lt;p&gt;I'm using this layout for now:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.                                       # project root                 
├── README.md                           # project README
├── LICENSE                             # project LICENSE
├── .gitignore                          # project .gitignore
├── Taskfile.yml                        # project taskfile
└── gcp                                 # GCP Terraform code
    └── base                            # base GCP Terraform code
        ├── README.md                   # base README
        ├── .terraform-version          # base terraform version
        ├── main.tf                     # base main.tf
        ├── outputs.tf                  # base outputs.tf
        ├── variables.tf                # base variables.tf
        ├── terraform.tfvars            # base terraform.tfvars
        └── provider.tf                 # base provider.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let me quickly explain. The &lt;code&gt;gcp&lt;/code&gt; directory is the root directory for all GCP Terraform code. The &lt;code&gt;base&lt;/code&gt; directory is the root directory for all base GCP Terraform code. That base code would cover the following, for now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a bucket for storing Terraform state&lt;/li&gt;
&lt;li&gt;Creating Key and KeyRing for encrypting Terraform state file&lt;/li&gt;
&lt;li&gt;Setting up Terraform's backend&lt;/li&gt;
&lt;li&gt;Setting up Terraform provider&lt;/li&gt;
&lt;li&gt;Setting up Terraform base variables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just create the directories and files, and we are ready to go.&lt;/p&gt;

&lt;p&gt;
  How I use tfenv?
  &lt;br&gt;
Oh, one small thing. The file &lt;code&gt;.terraform-version&lt;/code&gt; is used by &lt;code&gt;tfenv&lt;/code&gt; to set and pin the Terraform version. Even if I set with &lt;code&gt;tfenv&lt;/code&gt; the default Terraform version, I use this file to say which Terraform version should be used for the base. If you are using &lt;code&gt;tfenv&lt;/code&gt; do:&lt;br&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;$ &lt;/span&gt;&lt;span&gt;cd &lt;/span&gt;gcp/base
&lt;span&gt;$ &lt;/span&gt;tfenv pin
Pinned version by writing &lt;span&gt;"1.5.5"&lt;/span&gt; to /somepath/gcp/base/.terraform-version
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;With this, it does not matter what is the default Terraform version. The &lt;code&gt;tfenv&lt;/code&gt; will use the version from the &lt;code&gt;.terraform-version&lt;/code&gt; file. If, for some reason, you&lt;br&gt;
do not have the desired version, &lt;code&gt;tfenv&lt;/code&gt; will install it for you.&lt;br&gt;
&lt;/p&gt;

&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up Provider
&lt;/h3&gt;

&lt;p&gt;The first thing we need to do is to set up the provider. I'm using the latest version of the provider. You can find the latest version &lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest"&gt;here&lt;/a&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;provider&lt;/span&gt; &lt;span class="s2"&gt;"google"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project_id&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
  &lt;span class="nx"&gt;zone&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;=1.5.5"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/google"&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;"4.77.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"local"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"local-state.tfstate"&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;I'm using the &lt;code&gt;local&lt;/code&gt; backend for now. I plan to switch to the &lt;code&gt;gcs&lt;/code&gt; backend later. The &lt;code&gt;local&lt;/code&gt; backend is used for storing the Terraform state locally. The state file is called &lt;code&gt;local-state.tfstate&lt;/code&gt;. And it will contain the Terraform state for the base. It is not encrypted. My plan is to keep it safe and encrypted.&lt;/p&gt;

&lt;p&gt;Block &lt;code&gt;terraform&lt;/code&gt; sets the required Terraform provider version, provider, and backend. You can find more &lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;provider&lt;/code&gt; block configures the provider by setting the project ID, region, and zone. You probably noticed that I'm using variables.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up Variables
&lt;/h3&gt;

&lt;p&gt;I'm using variables for defining variables I'll be using in the base. I'm using the following variables:&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;"project_id"&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;"project id"&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;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"default 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;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;"zone"&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;"default zone"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once you defined the variables, you need to set them. You can do that in the &lt;code&gt;terraform.tfvars&lt;/code&gt; 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;project_id&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-project"&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt;     &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"europe-west1"&lt;/span&gt;
&lt;span class="nx"&gt;zone&lt;/span&gt;       &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"europe-west1-b"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You should avoid committing the &lt;code&gt;terraform.tfvars&lt;/code&gt; file. It may contain sensitive information. I'll show you how to handle that later. But for now, you must create the &lt;code&gt;terraform.tfvars&lt;/code&gt; file and set the variables. For the &lt;code&gt;project_id&lt;/code&gt; variable, you need to set the project ID you created earlier. Choose the region and zone that is closest to you. You can find the list of regions and zones &lt;a href="https://cloud.google.com/compute/docs/regions-zones"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;
  Regions&amp;amp;Zones Important?
  &lt;br&gt;
Is this important? Yes, it is.

&lt;p&gt;You need to choose the region and zone carefully, considering reliability, availability, and cost into account. For your users to have the best experience, your servers must be close to them and deliver the content quickly. It would be best if you considered the cost, too.&lt;/p&gt;

&lt;p&gt;To learn more about choosing the correct region and zone, you can find more &lt;a href="https://cloud.google.com/solutions/best-practices-compute-engine-region-selection"&gt;here in Google documentation&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;/p&gt;
&lt;h3&gt;
  
  
  Init, Plan, and Apply
&lt;/h3&gt;

&lt;p&gt;At this moment, I can initialize the Terraform workflow:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform init


Initializing the backend...

Successfully configured the backend &lt;span class="s2"&gt;"local"&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Finding hashicorp/google versions matching &lt;span class="s2"&gt;"4.77.0"&lt;/span&gt;...
- Installing hashicorp/google v4.77.0...
- Installed hashicorp/google v4.77.0 &lt;span class="o"&gt;(&lt;/span&gt;signed by HashiCorp&lt;span class="o"&gt;)&lt;/span&gt;

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file &lt;span class="k"&gt;in &lt;/span&gt;your version control repository
so that Terraform can guarantee to make the same selections by default when
you run &lt;span class="s2"&gt;"terraform init"&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running &lt;span class="s2"&gt;"terraform plan"&lt;/span&gt; to see any changes that are required &lt;span class="k"&gt;for &lt;/span&gt;your infrastructure. All Terraform commands
should now work.

If you ever &lt;span class="nb"&gt;set &lt;/span&gt;or change modules or backend configuration &lt;span class="k"&gt;for &lt;/span&gt;Terraform,
rerun this &lt;span class="nb"&gt;command &lt;/span&gt;to reinitialize your working directory. If you forget, other
commands will detect it and remind you to &lt;span class="k"&gt;do &lt;/span&gt;so &lt;span class="k"&gt;if &lt;/span&gt;necessary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will create a directory &lt;code&gt;.terraform&lt;/code&gt; and file &lt;code&gt;.terraform.lock.hcl&lt;/code&gt; file. You should include &lt;code&gt;.terraform.lock.hcl&lt;/code&gt; in your commit. It is used to lock the provider version. If you run &lt;code&gt;terraform init&lt;/code&gt; again, it will use the same provider version. The folder &lt;code&gt;.terraform&lt;/code&gt; stores the provider plugins and other files required for Terraform to work.&lt;/p&gt;

&lt;p&gt;Next, I can run &lt;code&gt;terraform plan&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform plan

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will show me what Terraform will do. Since I did not create any resources, it will not do anything. But it is good to run &lt;code&gt;terraform plan&lt;/code&gt; before &lt;code&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next, I can run &lt;code&gt;terraform apply&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 0 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Well, this is expected. I did not create any resources. If there are any changes, Terraform will show you what it will do and ask you to confirm them. If you want to apply the changes, type &lt;code&gt;yes&lt;/code&gt; &lt;br&gt;
and hit enter.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up The Bucket
&lt;/h3&gt;

&lt;p&gt;From the &lt;code&gt;terraform init&lt;/code&gt; output, I can see that the backend is configured. It is configured to use the &lt;code&gt;local&lt;/code&gt; backend. My goal is to use the &lt;code&gt;gcs&lt;/code&gt; backend. That means I want to store the Terraform state file in the GCP bucket. Let's look at how to do 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;data&lt;/span&gt; &lt;span class="s2"&gt;"google_storage_project_service_account"&lt;/span&gt; &lt;span class="s2"&gt;"gcs_account"&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;"google_kms_key_ring"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states"&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;"terraform-state-key-ring"&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;"us"&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;prevent_destroy&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;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_kms_crypto_key"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states"&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;"terraform-state-key"&lt;/span&gt;
  &lt;span class="nx"&gt;key_ring&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_kms_key_ring&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf_states&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;rotation_period&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"100000s"&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;prevent_destroy&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;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"google_kms_crypto_key_iam_binding"&lt;/span&gt; &lt;span class="s2"&gt;"binding"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;crypto_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google_kms_crypto_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf_states&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/cloudkms.cryptoKeyEncrypterDecrypter"&lt;/span&gt;

  &lt;span class="nx"&gt;members&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"serviceAccount:${data.google_storage_project_service_account.gcs_account.email_address}"&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;"google_storage_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states_bucket"&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;"terraform-states-${var.project_id}"&lt;/span&gt;
  &lt;span class="nx"&gt;force_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;"us"&lt;/span&gt;
  &lt;span class="nx"&gt;storage_class&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;versioning&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;encryption&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;default_kms_key_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-crypto-key-id"&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;google_kms_crypto_key_iam_binding&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;prevent_destroy&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;When working with Terraform, you are calling the API of the provider. With each call, you are pushing configuration to the provider. The provider will then create, update, or delete your resources.&lt;/p&gt;

&lt;p&gt;That is a lot of code. Let me explain.&lt;/p&gt;

&lt;p&gt;First, I'm creating a Key(&lt;code&gt;google_kms_crypto_key&lt;/code&gt;) and Key Ring(&lt;code&gt;google_kms_key_ring&lt;/code&gt;) for encrypting the Terraform state file. These resources are coming from &lt;a href="https://cloud.google.com/kms/docs"&gt;Google KMS&lt;/a&gt;. The service allows you to work with cryptographic keys and execute cryptographic operations.&lt;/p&gt;

&lt;p&gt;Next is adding the Role &lt;code&gt;roles/cloudkms.cryptoKeyEncrypterDecrypter&lt;/code&gt; to the Service Account. Which is needed to encrypt/decrypt the state file. In the end, I'm creating a bucket for storing the Terraform state file, and I'm using the Key for encrypting the Terraform state file. There is a small gotcha. I need a Service Account for encrypting the Terraform state file. But it does not exist&lt;br&gt;
yet. So, I'll use &lt;a href="https://cloud.google.com/storage/docs/projects#service-accounts"&gt;an automatic Google Cloud Storage service account&lt;/a&gt; to get it when the resource is created. I'm using a &lt;code&gt;data&lt;/code&gt; resource for that.&lt;/p&gt;

&lt;p&gt;I'm using &lt;code&gt;lifecycle.prevent_destroy&lt;/code&gt; to ensure the resources are not accidentally deleted.&lt;/p&gt;

&lt;p&gt;If I now run the &lt;code&gt;terraform plan&lt;/code&gt; I'll see the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform plan
data.google_storage_project_service_account.gcs_account: Reading...
data.google_storage_project_service_account.gcs_account: Read &lt;span class="nb"&gt;complete &lt;/span&gt;after 1s &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;service-964026605440@gs-project-accounts.iam.gserviceaccount.com]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  &lt;span class="c"&gt;# google_kms_crypto_key.tf_states will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"google_kms_crypto_key"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + destroy_scheduled_duration &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;                         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + import_only                &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + key_ring                   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + name                       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-state-key"&lt;/span&gt;
      + purpose                    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ENCRYPT_DECRYPT"&lt;/span&gt;
      + rotation_period            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"100000s"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# google_kms_crypto_key_iam_binding.binding will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"google_kms_crypto_key_iam_binding"&lt;/span&gt; &lt;span class="s2"&gt;"binding"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + crypto_key_id &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + etag          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + members       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
          + &lt;span class="s2"&gt;"serviceAccount:service-964026605440@gs-project-accounts.iam.gserviceaccount.com"&lt;/span&gt;,
        &lt;span class="o"&gt;]&lt;/span&gt;
      + role          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"roles/cloudkms.cryptoKeyEncrypterDecrypter"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# google_kms_key_ring.tf_states will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"google_kms_key_ring"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + location &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us"&lt;/span&gt;
      + name     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-state-key-ring"&lt;/span&gt;
      + project  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# google_storage_bucket.tf_states_bucket will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"google_storage_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states_bucket"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + force_destroy               &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;                          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + labels                      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + location                    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"US"&lt;/span&gt;
      + name                        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-states-project-id"&lt;/span&gt;
      + project                     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + public_access_prevention    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + self_link                   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + storage_class               &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"STANDARD"&lt;/span&gt;
      + uniform_bucket_level_access &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + url                         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;

      + encryption &lt;span class="o"&gt;{&lt;/span&gt;
          + default_kms_key_name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-crypto-key-id"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;

      + versioning &lt;span class="o"&gt;{&lt;/span&gt;
          + enabled &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

Plan: 4 to add, 0 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn&lt;span class="s1"&gt;'t use the -out option to save this plan, so Terraform can'&lt;/span&gt;t guarantee to take exactly these actions &lt;span class="k"&gt;if &lt;/span&gt;you run &lt;span class="s2"&gt;"terraform apply"&lt;/span&gt; now.  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Ok, let's try to apply the changes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply

...

Plan: 4 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only &lt;span class="s1"&gt;'yes'&lt;/span&gt; will be accepted to approve.

  Enter a value: &lt;span class="nb"&gt;yes

&lt;/span&gt;google_kms_key_ring.tf_states: Creating...

Error: Error creating KeyRing: googleapi: Error 403: Google Cloud KMS API has not been used &lt;span class="k"&gt;in &lt;/span&gt;project 3534534535 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/cloudkms.googleapis.com/overview?project&lt;span class="o"&gt;=&lt;/span&gt;3534534535 &lt;span class="k"&gt;then &lt;/span&gt;retry. If you enabled this API recently, &lt;span class="nb"&gt;wait &lt;/span&gt;a few minutes &lt;span class="k"&gt;for &lt;/span&gt;the action to propagate to our systems and retry.

  with google_kms_key_ring.tf_states,
  on main.tf line 4, &lt;span class="k"&gt;in &lt;/span&gt;resource &lt;span class="s2"&gt;"google_kms_key_ring"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states"&lt;/span&gt;:
   4: resource &lt;span class="s2"&gt;"google_kms_key_ring"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I had to cut output. But you can see the error. I need to enable the Cloud KMS API. This will be a standard message if you try to use the API, which is not enabled. You can enable it by following the link in the error message. Or you can enable it with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud services &lt;span class="nb"&gt;enable &lt;/span&gt;cloudkms.googleapis.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;
  gcloud CLI useful commands
  &lt;br&gt;
You can see all the APIs you enabled with the following command:&lt;br&gt;

&lt;pre&gt;&lt;code&gt;&lt;span&gt;$ &lt;/span&gt;gcloud services list &lt;span&gt;--enabled&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;And list all available APIs with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;$ &lt;/span&gt;gcloud services list &lt;span&gt;--available&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;




&lt;/p&gt;

&lt;p&gt;I already have enabled Google Storage API. But if you did not, you need to enable it, too. Try to figure out how. Ok, let's try again:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply
...

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only &lt;span class="s1"&gt;'yes'&lt;/span&gt; will be accepted to approve.

  Enter a value: &lt;span class="nb"&gt;yes

&lt;/span&gt;google_kms_key_ring.tf_states: Creating...
╷
│ Error: Error creating KeyRing: googleapi: Error 409: KeyRing projects/project-id/locations/us/keyRings/terraform-state-key-ring already exists.
│
│   with google_kms_key_ring.tf_states,
│   on main.tf line 4, &lt;span class="k"&gt;in &lt;/span&gt;resource &lt;span class="s2"&gt;"google_kms_key_ring"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states"&lt;/span&gt;:
│    4: resource &lt;span class="s2"&gt;"google_kms_key_ring"&lt;/span&gt; &lt;span class="s2"&gt;"tf_states"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I was impatient and ran &lt;code&gt;terraform apply&lt;/code&gt; again. Google API responded that it was not enabled, but actually, it was. And not only that, it created the Key and KeyRing. This situation is rare. But it can happen. We can fix it by importing created resources. Let's do this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform import google_kms_key_ring.tf_states project-id/us/terraform-state-key-ring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;How do I know that? Well, I read the error message and the provider's documentation. You'll have a section &lt;strong&gt;Import&lt;/strong&gt; in Terraform documentation for each resource.&lt;br&gt;
There, you'll be able to see how to import it.&lt;/p&gt;

&lt;p&gt;Now, let's try again:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply

...
Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only &lt;span class="s1"&gt;'yes'&lt;/span&gt; will be accepted to approve.

  Enter a value: &lt;span class="nb"&gt;yes

&lt;/span&gt;data.google_storage_project_service_account.gcs_account: Reading...
google_kms_key_ring.tf_states: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects/project-id/locations/us/keyRings/terraform-state-key-ring]
data.google_storage_project_service_account.gcs_account: Read &lt;span class="nb"&gt;complete &lt;/span&gt;after 0s &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;service-964026605440@gs-project-accounts.iam.gserviceaccount.com]
google_kms_crypto_key.tf_states_key: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects/project-id/locations/us/keyRings/terraform-state-key-ring/cryptoKeys/terraform-states-key]
google_kms_crypto_key_iam_binding.binding: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;projects/project-id/locations/us/keyRings/terraform-state-key-ring/cryptoKeys/terraform-states-key/roles/cloudkms.cryptoKeyEncrypterDecrypter]
google_storage_bucket.tf_states_bucket: Creating...
google_storage_bucket.tf_states_bucket: Creation &lt;span class="nb"&gt;complete &lt;/span&gt;after 2s &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform-states-project-id]

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

&lt;/div&gt;


&lt;p&gt;The bucket is created. You'll see it if you go to &lt;a href="https://console.cloud.google.com/storage/"&gt;GCP console&lt;/a&gt;. You can check bucket properties and configuration there in the console or with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gsutil &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; gs://terraform-states-project-id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can see that the bucket is encrypted with the Key we created. And to check the Key properties and configuration in the console or with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud kms keys describe terraform-states-key &lt;span class="nt"&gt;--location&lt;/span&gt; us &lt;span class="nt"&gt;--keyring&lt;/span&gt; terraform-state-key-ring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can list the content of the bucket with the command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gsutil &lt;span class="nb"&gt;ls &lt;/span&gt;gs://terraform-states-project-id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But you'll get nothing because it is empty. I still need to move Terraform's state file to the bucket.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up The Backend
&lt;/h3&gt;

&lt;p&gt;If you remember, I'm using the &lt;code&gt;local&lt;/code&gt; backend. If you check the local directory, you'll see a file called &lt;code&gt;local-state.tfstate&lt;/code&gt;. That is the Terraform state file. And that file I want to move to &lt;br&gt;
the bucket and encrypt it there. I can do that with the following code:&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;backend&lt;/span&gt; &lt;span class="s2"&gt;"gcs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-states-project-id"&lt;/span&gt;
    &lt;span class="nx"&gt;prefix&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform/state"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;


&lt;p&gt;And removing this one in &lt;code&gt;provider.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;backend&lt;/span&gt; &lt;span class="s2"&gt;"local"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"local-state.tfstate"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, if I run &lt;code&gt;terraform init&lt;/code&gt;, I'll see the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init

Initializing the backend...
╷
│ Error: Backend configuration changed
│
│ A change &lt;span class="k"&gt;in &lt;/span&gt;the backend configuration has been detected, which may require migrating existing state.
│
│ If you wish to attempt automatic migration of the state, use &lt;span class="s2"&gt;"terraform init -migrate-state"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
│ If you wish to store the current configuration with no changes to the state, use &lt;span class="s2"&gt;"terraform init -reconfigure"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
╵
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Well, let's do as it says:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;terraform init &lt;span class="nt"&gt;-migrate-state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When asked, type &lt;code&gt;yes&lt;/code&gt; and wait to finish. Let's check if we have anything in the bucket:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gsutil &lt;span class="nb"&gt;ls &lt;/span&gt;gs://terraform-states-project-id/terraform/state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You'll see that the bucket has a file called &lt;code&gt;default.state&lt;/code&gt;. That is the Terraform state file. It is encrypted. You can check it with the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gsutil &lt;span class="nb"&gt;cat &lt;/span&gt;gs://terraform-states-project-id/terraform/state/default.tfstate | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://rnemet.substack.com/embed" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--ngDK5v8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://substackcdn.com/image/fetch/f_auto%2Cq_auto:best%2Cfl_progressive:steep/https%253A%252F%252Frnemet.substack.com%252Ftwitter%252Fsubscribe-card.jpg%253Fv%253D-1774894496%2526version%253D9" height="417" class="m-0" width="800"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://rnemet.substack.com/embed" rel="noopener noreferrer" class="c-link"&gt;
          DevCube | Robert Nemet | Substack
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Weekly rant about software design, devops, kubernetes, sre... Click to read DevCube, by Robert Nemet, a Substack publication. Launched 6 months ago.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--hjtaVPaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://substackcdn.com/image/fetch/f_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fsubstack-post-media.s3.amazonaws.com%252Fpublic%252Fimages%252F62a252ac-a23f-428d-8013-8216e8ad1baa%252Ffavicon.ico" width="64" height="64"&gt;
        rnemet.substack.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



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

&lt;p&gt;With I just set up a project. With it, I can start exploring GCP. We touched basic Terraform CLI commands(&lt;code&gt;init&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt;, and &lt;code&gt;import&lt;/code&gt;), Terraform variables, provider, backend, and resources. I hope you learned something new. I know I did. I'll continue exploring GCP with Terraform in the next article.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/"&gt;Google Cloud Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://console.cloud.google.com/"&gt;Google Cloud Console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/sdk/docs"&gt;Google Cloud SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.terraform.io/"&gt;Terrafom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs"&gt;GCP Terraform Provider&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>gcp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Task vs Make - Final Thoughts</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Thu, 10 Aug 2023 05:30:00 +0000</pubDate>
      <link>https://forem.com/madmaxx/task-vs-make-final-thoughts-2bb2</link>
      <guid>https://forem.com/madmaxx/task-vs-make-final-thoughts-2bb2</guid>
      <description>&lt;p&gt;So, after spending time moving from &lt;em&gt;Make&lt;/em&gt; to &lt;em&gt;Task&lt;/em&gt;, I decided. I'm going with &lt;em&gt;Task&lt;/em&gt; in my future projects. &lt;em&gt;Make&lt;/em&gt; will not be removed from my projects immediately, but I will not use it in the future. Let me tell you why.&lt;/p&gt;

&lt;p&gt;All this is my personal opinion. I'm not saying that &lt;em&gt;Task&lt;/em&gt; is better than &lt;em&gt;Make&lt;/em&gt;. I think that &lt;em&gt;Task&lt;/em&gt; is better for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Taskfile
&lt;/h3&gt;

&lt;p&gt;I have a global &lt;em&gt;Task&lt;/em&gt; in my home directory for standard stuff. It contains tasks to &lt;em&gt;update my tools&lt;/em&gt;, &lt;em&gt;run some diagnostics&lt;/em&gt;, etc. Because it is global, it is accessible from any directory. For example:&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&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;dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{.USER_WORKING_DIR}}'&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;go build . -o app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above example, &lt;code&gt;task -g build&lt;/code&gt; will build the Golang app in my current working directory. I do not need to add &lt;em&gt;Task&lt;/em&gt; for every project. I must add &lt;em&gt;Taskfile&lt;/em&gt; to the project if I share it. But, as long it is experimental, there is no need. Anyway, storing everyday tasks as global is a nice feature.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;{{.USER_WORKING_DIR}}&lt;/code&gt; is a variable set by &lt;em&gt;Task&lt;/em&gt;. It is the current working directory. And the &lt;code&gt;dir&lt;/code&gt; slug defines the folder where the task will be executed. In this case, it is the current working directory. But you can set it to any folder you want. So you do not need to switch directories to run a task. Just set the &lt;code&gt;dir&lt;/code&gt; slug to the folder where you want to run the job. Neat!&lt;/p&gt;

&lt;h3&gt;
  
  
  Including/Grouping Task Files
&lt;/h3&gt;

&lt;p&gt;The explicit inclusion of other &lt;em&gt;Taskfiles&lt;/em&gt; with namespace is really neat. This way, you get a context of tasks. For example:&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;includes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./Taskfile.tools.yml&lt;/span&gt;
  &lt;span class="na"&gt;docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./docs/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assuming I have the task &lt;code&gt;install_tools&lt;/code&gt; in the file &lt;code&gt;Taskfile.tools.yml&lt;/code&gt;, I can run it with &lt;code&gt;task tools:install_tools&lt;/code&gt;. Also, suppose I have &lt;code&gt;Taskfile.yml&lt;/code&gt; file in the &lt;code&gt;docs&lt;/code&gt; directory. I can run it with &lt;code&gt;task docs:build_docs&lt;/code&gt; from the root directory. This way, I'm groping tasks by context.&lt;/p&gt;

&lt;p&gt;In case you have a CI pipeline and need to do builds for different platforms. You can split your tasks into different files with the platform postfix. For example, you have different tasks for &lt;code&gt;linux&lt;/code&gt; and &lt;code&gt;windows&lt;/code&gt; platforms. You can split them into separate files. I name them &lt;code&gt;Taskfile_linux.yml&lt;/code&gt; and &lt;code&gt;Taskfile_windows.yml&lt;/code&gt;. Then you can include them in the main &lt;code&gt;Taskfile.yml&lt;/code&gt; file with the following:&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;includes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./Taskfile_{{OS}}.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run &lt;code&gt;task build:build&lt;/code&gt; on the &lt;code&gt;linux&lt;/code&gt; platform, it will include &lt;code&gt;Taskfile_linux.yml&lt;/code&gt; and run the &lt;code&gt;build&lt;/code&gt; task from it. If you run the same task on &lt;code&gt;windows&lt;/code&gt; platform, it will consist of &lt;code&gt;Taskfile_windows.yml&lt;/code&gt; and run the &lt;code&gt;build&lt;/code&gt; job from it. This way, you can have different tasks for different platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task Dependencies and Skipping Done Tasks
&lt;/h3&gt;

&lt;p&gt;Task dependencies are really nice. You can define a task that depends on other tasks. For example:&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;manifests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate manifests e.g. CRD, RBAC etc.&lt;/span&gt;
    &lt;span class="na"&gt;deps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tools:controller-gen&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases&lt;/span&gt;
    &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main.go&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apis/**/*.go&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pkg/**/*.go&lt;/span&gt;
    &lt;span class="na"&gt;generates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;config/crd/bases/**/*.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;manifests&lt;/code&gt; task in the above example depends on the &lt;code&gt;tools:controller-gen&lt;/code&gt; task. Dependency tasks are run in parallel. This way, you can define tasks that rely on other tasks.&lt;/p&gt;

&lt;p&gt;Also, the above example shows how to define sources and generated files. The &lt;code&gt;sources&lt;/code&gt; slug defines files used to generate other files. The &lt;code&gt;generates&lt;/code&gt; slug defines files that are generated by the task. This way, you can tell &lt;em&gt;Task&lt;/em&gt; which files are used to generate other files. It is to determine whether the task needs to be run. &lt;em&gt;Task&lt;/em&gt; will generate a fingerprint for sources and compare it next time the task&lt;br&gt;
needs to be run. The task is considered done if the fingerprint is not changed, assuming the generated files are present.&lt;/p&gt;

&lt;p&gt;Also, there is an alternative with a &lt;code&gt;status&lt;/code&gt; slug. You can define a command that will return &lt;code&gt;0&lt;/code&gt; if the task is done. For example:&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;install-cert-manager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install cert-manager&lt;/span&gt;    
    &lt;span class="na"&gt;deps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;init-cluster&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/{{.CERT_MANAGER_VERSION}}/cert-manager.yaml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Waiting for cert-manager to be ready" &amp;amp;&amp;amp; sleep &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kubectl -n cert-manager get pods | grep Running | wc -l | grep -q &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Golang Template Engine
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Task&lt;/em&gt; uses the Golang template engine. As a Golang user, this is a big plus. I can use all the features of the Golang template engine plus one that &lt;em&gt;Task&lt;/em&gt; provides, like including functions from &lt;code&gt;https://go-task.github.io/slim-sprig/&lt;/code&gt;. For example:&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;print-date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo {{now | date "2006-01-02"}}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo '{{OS}} {{ARCH}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Format Is YAML
&lt;/h3&gt;

&lt;p&gt;I like it when a file has a structure and text editors can understand it. I can understand it as well. It is enough said.&lt;/p&gt;

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

&lt;p&gt;There are other things that I like about &lt;em&gt;Task&lt;/em&gt; but I will not go into details. I have covered the most important ones, at least for me.&lt;/p&gt;

&lt;p&gt;While evaluating &lt;em&gt;Makefile&lt;/em&gt; and &lt;em&gt;Task&lt;/em&gt;, I discovered another tool called &lt;a href="https://just.systems/man/en/chapter_1.html"&gt;&lt;em&gt;Just&lt;/em&gt;&lt;/a&gt;. It is more similar to &lt;em&gt;Make&lt;/em&gt; than &lt;em&gt;Task&lt;/em&gt;. For that reason, I did not want to spend a lot of time on it. But I see it as a good alternative to &lt;em&gt;Make&lt;/em&gt;. Especially if you do not like YAML.&lt;/p&gt;

&lt;p&gt;Let me know what you think. Thanks for reading. Enjoy!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://taskfile.dev/"&gt;Taskfile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/go-task/task"&gt;Taskfile Github&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>automation</category>
      <category>tooling</category>
    </item>
    <item>
      <title>WIP: Taskfile instead of Makefile?</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Tue, 01 Aug 2023 05:30:00 +0000</pubDate>
      <link>https://forem.com/madmaxx/wip-taskfile-instead-of-makefile-4gn9</link>
      <guid>https://forem.com/madmaxx/wip-taskfile-instead-of-makefile-4gn9</guid>
      <description>&lt;p&gt;Recently, I stumbled upon a tool called &lt;a href="https://taskfile.dev/#/"&gt;Taskfile&lt;/a&gt;. It is a task runner, similar to Makefile, but with many improvements. I decided to try it and see if it can replace my Makefiles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Taskfile?
&lt;/h2&gt;

&lt;p&gt;First of all, Makefile is a good tool. Today, you can use it on any platform, not just Linux. It is a standard tool for running tasks. It is flexible and powerful. My relation with it is love/hate. It can be tricky to write a good Makefile or to debug it. Reading also can be a challenge. Because of that, when I found out about Taskfile, I decided to try it.&lt;/p&gt;

&lt;h2&gt;
  
  
  First impressions
&lt;/h2&gt;

&lt;p&gt;Taskfile is written in Go. It is a single binary that you can download and use. If you can run Go programs, you can run Taskfile. It is a big plus for me. See &lt;a href="https://taskfile.dev/#/installation"&gt;docs on how to install it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Taskfile is a YAML file. Most of us are familiar with YAML, and many tools use it. It is easy to read, write, and parse, and IDEs have good support. Sometimes, it feels like we are programming in YAML. If you do not believe me, check out your CI/CD pipeline. I bet it is written in YAML.&lt;/p&gt;

&lt;p&gt;You can use auto-completion if you use ZSH, Bash, Fish, or PowerShell. It is a big plus for me. I like to have auto-completion for my tools. It makes my life easier, especially when you start adding more and more tasks. When you have a lot of tasks, they can be grouped into namespaces, hidden from a user(internal task), global, or local, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge: automate testing for Klock
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/robert-nemet/klock"&gt;Klock&lt;/a&gt; is my pet project. It is a &lt;code&gt;ValidatingAdmissionWebhook&lt;/code&gt; for Kubernetes. It enables locking Kubernetes resources. You can read more here &lt;a href="https://rnemet.dev/posts/projects/klock/"&gt;Klock - Kubernetes locking&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of the things that was on my to-do list was to automate testing. I'm doing it locally because it is easier and faster. But still, it is cumbersome to start the &lt;a href="https://kind.sigs.k8s.io/"&gt;Kind cluster&lt;/a&gt;, deploy Klock, and run tests. I decided to use Taskfile to automate it.&lt;/p&gt;

&lt;p&gt;What I need to do is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start Kind cluster&lt;/li&gt;
&lt;li&gt;Install Cert Manager&lt;/li&gt;
&lt;li&gt;Build K8s manifests&lt;/li&gt;
&lt;li&gt;Build the Docker image if there are any code/configuration changes&lt;/li&gt;
&lt;li&gt;Load the image into a Kind cluster&lt;/li&gt;
&lt;li&gt;Run tests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can find my initial work on the git branch &lt;a href="https://github.com/robert-nemet/klock/tree/migrate_taskfile"&gt;migrate_taskfile&lt;/a&gt;. If you compare it with the &lt;strong&gt;main&lt;/strong&gt; branch, you'll notice I added a new file called &lt;code&gt;Taskfile.yml&lt;/code&gt;. It is a main Taskfile. Also, I added the folder &lt;code&gt;tasks&lt;/code&gt; with two files, &lt;code&gt;Taskfile.testing.yml&lt;/code&gt; and &lt;code&gt;Taskfile.tools.yml&lt;/code&gt;. They are imported into the main Taskfile. The idea is to have a main Taskfile that will import other Taskfiles, while other Taskfiles will contain specific tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Kind cluster
&lt;/h3&gt;

&lt;p&gt;First, I need to start the Kind cluster:&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="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;KIND_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;v0.20.0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;go1.20.5"&lt;/span&gt;
  &lt;span class="na"&gt;CERT_MANAGER_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1.12.0&lt;/span&gt;
  &lt;span class="na"&gt;IMG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;controller:latest&lt;/span&gt;

&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;init-cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a kind cluster named klock&lt;/span&gt;
    &lt;span class="na"&gt;preconditions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;sh&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kind version | grep "{{.KIND_VERSION}}"&lt;/span&gt;
        &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;does&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;match&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{.KIND_VERSION}}.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Please&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;right&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;kind"&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kind create cluster --name klock&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kind get clusters | grep klock&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The slug &lt;code&gt;tasks&lt;/code&gt; contain a list of tasks. This task is called &lt;code&gt;init-cluster&lt;/code&gt;. It has a description, preconditions, commands, and status. For any task &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;cmds&lt;/code&gt; slugs are mandatory. Others are optional. What they do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cmds&lt;/code&gt; - list of commands that will be executed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;desc&lt;/code&gt; - description of the task, it will be shown when you run list tasks: &lt;code&gt;task -l&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;preconditions&lt;/code&gt; - list of preconditions that must be met before running the task. If any of the preconditions fail, the task will not be executed. In this case, I check if the Kind version is the same as the one I'm using. If not, the task will fail with the message.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; - list of commands that will be run to see if a task can be considered done. That means the task is done if the Kind cluster named &lt;code&gt;klock&lt;/code&gt; exists. It does not care how a cluster is created. It just checks if it exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice block &lt;code&gt;vars&lt;/code&gt;. It is a list of variables that can be used in the Taskfile. In this case, I'm using &lt;code&gt;KIND_VERSION&lt;/code&gt; to check if the Kind version is the same as the one I'm using. The Taskfile uses [Go template (&lt;a href="https://golang.org/pkg/text/template/"&gt;https://golang.org/pkg/text/template/&lt;/a&gt;) to render variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Cert Manager
&lt;/h3&gt;

&lt;p&gt;Next, I need to install &lt;a href="https://cert-manager.io/"&gt;Cert Manager&lt;/a&gt;:&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;install-cert-manager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install cert-manager&lt;/span&gt;    
    &lt;span class="na"&gt;deps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;init-cluster&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/{{.CERT_MANAGER_VERSION}}/cert-manager.yaml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Waiting for cert-manager to be ready" &amp;amp;&amp;amp; sleep &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kubectl -n cert-manager get pods | grep Running | wc -l | grep -q &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new thing here is the &lt;code&gt;deps&lt;/code&gt; slug. It is a list of tasks that must be executed before this task. In this case, I need to create a Kind cluster before I can install Cert Manager. Again, I'm using a variable &lt;code&gt;CERT_MANAGER_VERSION&lt;/code&gt; to install the correct version of Cert Manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build K8s manifests
&lt;/h3&gt;

&lt;p&gt;To generate manifests, I'm using &lt;a href="https://book.kubebuilder.io/reference/controller-gen.html"&gt;kubebuilder tool contrller-gen&lt;/a&gt;:&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;manifests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate manifests e.g. CRD, RBAC etc.&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases&lt;/span&gt;
    &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main.go&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apis/**/*.go&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pkg/**/*.go&lt;/span&gt;
    &lt;span class="na"&gt;generates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;config/crd/bases/**/*.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time I'm using &lt;code&gt;sources&lt;/code&gt; and &lt;code&gt;generates&lt;/code&gt; slugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sources&lt;/code&gt; is a list of files that will be used to generate manifests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generates&lt;/code&gt; is a list of files that will be generated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, Taskfile will generate a checksum for sources from the previous run. If the checksum is the same, it will not run the task. It will just print out that the task is done. If the checksum is different, it will run the task. It is a nice feature that can save you some time. &lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Klock to Kind cluster
&lt;/h3&gt;

&lt;p&gt;To deploy Klock to the Kind cluster, I'm using &lt;a href="https://kustomize.io/"&gt;kustomize&lt;/a&gt;:&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;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy the controller to the kind cluster and wait for it to be ready. Use IMG to specify image name&lt;/span&gt;
    &lt;span class="na"&gt;deps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;install-cert-manager&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;manifests&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker-build&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kind-load&lt;/span&gt;
        &lt;span class="na"&gt;silent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cd config/manager &amp;amp;&amp;amp; kustomize edit set image controller={{.IMG}}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kustomize build config/default | kubectl apply -f -&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Waiting for controller to be ready" &amp;amp;&amp;amp; sleep &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kubectl get pods -n klock-system | grep klock-controller | wc -l | grep -q &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This task depends on &lt;code&gt;install-cert-manager&lt;/code&gt;, &lt;code&gt;manifests&lt;/code&gt;, &lt;code&gt;docker-build&lt;/code&gt;, and &lt;code&gt;kind-load&lt;/code&gt; tasks. It means that those tasks have to be executed before this task.&lt;/p&gt;

&lt;p&gt;But a small catch, &lt;code&gt;kind-load&lt;/code&gt; can be done only if &lt;code&gt;docker-build&lt;/code&gt; is done successfully. Other tasks can be executed in parallel. That is why other tasks are in the &lt;code&gt;deps&lt;/code&gt; slug. Tasks in the &lt;code&gt;deps&lt;/code&gt; slug will be executed in parallel. Tasks will be executed in order while in the &lt;code&gt;cmds&lt;/code&gt; slug.&lt;/p&gt;

&lt;p&gt;I'm adding a new slug, &lt;code&gt;silent&lt;/code&gt; to suppress the task output.&lt;/p&gt;

&lt;p&gt;This way, I managed to do some tasks in parallel and speed up the process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run tests
&lt;/h3&gt;

&lt;p&gt;To run tests I'm using &lt;a href="https://kuttl.dev/"&gt;KUTTL&lt;/a&gt;:&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;ktest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;desc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run kuttl tests. Specify image name with IMG&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
        &lt;span class="na"&gt;silent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kubectl kuttl test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is nothing special here. Except I should move the &lt;code&gt;deploy&lt;/code&gt; task to the &lt;code&gt;deps&lt;/code&gt; slug. But this is still a work in progress.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting It All Together
&lt;/h3&gt;

&lt;p&gt;My main &lt;code&gt;Taskfile&lt;/code&gt; looks like this:&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="c1"&gt;# https://taskfile.dev&lt;/span&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;

&lt;span class="na"&gt;includes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tasks/Taskfile.tools.yml&lt;/span&gt;
  &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tasks/Taskfile.testing.yml&lt;/span&gt;

&lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;silent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;cmds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "Welcome to Klock!"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;task -l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm including two other &lt;code&gt;Taskfiles&lt;/code&gt;. One is for tools, and the other is for tests. The &lt;code&gt;default&lt;/code&gt; task is to print out a welcome message and list all tasks. Invoking the &lt;code&gt;task&lt;/code&gt; without any arguments, it will run the &lt;code&gt;default&lt;/code&gt; task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;task
Welcome to Klock!
task: Available tasks &lt;span class="k"&gt;for &lt;/span&gt;this project:
&lt;span class="k"&gt;*&lt;/span&gt; tests:cleanup:                    Delete the kind cluster named klock
&lt;span class="k"&gt;*&lt;/span&gt; tests:deploy:                     Deploy the controller to the kind cluster and &lt;span class="nb"&gt;wait &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;it to be ready. Use IMG to specify image name
&lt;span class="k"&gt;*&lt;/span&gt; tests:docker-build:               Build the docker image, specify image name with IMG
&lt;span class="k"&gt;*&lt;/span&gt; tests:docker-push:                Push the docker image, specify image name with IMG
&lt;span class="k"&gt;*&lt;/span&gt; tests:init-cluster:               Create a kind cluster named klock
&lt;span class="k"&gt;*&lt;/span&gt; tests:install-cert-manager:       Install cert-manager
&lt;span class="k"&gt;*&lt;/span&gt; tests:kind-load:                  Load the docker image into the kind cluster, specify image name with IMG
&lt;span class="k"&gt;*&lt;/span&gt; tests:ktest:                      Run kuttl tests. Specify image name with IMG
&lt;span class="k"&gt;*&lt;/span&gt; tests:manifests:                  Generate manifests e.g. CRD, RBAC etc.
&lt;span class="k"&gt;*&lt;/span&gt; tests:publish:                    Run tests and publish the docker image. Specify image name with IMG and version with VERSION
&lt;span class="k"&gt;*&lt;/span&gt; tests:undeploy:                   Undeploy the controller from the kind cluster
&lt;span class="k"&gt;*&lt;/span&gt; tools:controller-gen:             Download controller-gen locally &lt;span class="k"&gt;if &lt;/span&gt;necessary.
&lt;span class="k"&gt;*&lt;/span&gt; tools:create-localbin:            Create the localbin directory
&lt;span class="k"&gt;*&lt;/span&gt; tools:delete-kustomize:           Delete kustomize
&lt;span class="k"&gt;*&lt;/span&gt; tools:envtest:                    Download envtest-setup locally &lt;span class="k"&gt;if &lt;/span&gt;necessary.
&lt;span class="k"&gt;*&lt;/span&gt; tools:install-kustomize:          Install kustomize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run the tests, I'm using &lt;code&gt;task tests:ktest IMG=rnemet/klock:test&lt;/code&gt;. It will run the &lt;code&gt;tests:ktest&lt;/code&gt; task and pass the image name to the task. This image name is propagated to other tasks that need it. As you can see, I grouped tasks into namespaces. I'm using the &lt;code&gt;tests&lt;/code&gt; namespace for tasks related to tests and the &lt;code&gt;tools&lt;/code&gt; namespace for tasks related to tools.&lt;/p&gt;

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

&lt;p&gt;I'm still learning Taskfile. There are better ways to do things. But I'm pleased with the result. Still, I need to migrate other Makefile targets to Taskfile. But the results are promising. Changes are more readable, easier to maintain and to understand. When I migrate the rest of the Makefile targets, I can compare Taskfile and Makefile with more insights. But at the moment, Taskfile is a winner.&lt;/p&gt;

&lt;p&gt;Looking into &lt;a href="https://github.com/go-task/task"&gt;Taskfile Github repo&lt;/a&gt;, I can see that the project is active, and much work is underway. I'm looking forward to new features and improvements.&lt;/p&gt;

&lt;p&gt;This is a work in progress. I already have ideas on how to make it better. But I will leave that for another blog post. Meanwhile, you can check Taskfile and let me know what you think. Thanks for reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://taskfile.dev/"&gt;Taskfile&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>tooling</category>
      <category>automation</category>
    </item>
    <item>
      <title>Learning eBPF: Maps, Ring Buffers and Output</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Sat, 22 Jul 2023 16:14:51 +0000</pubDate>
      <link>https://forem.com/madmaxx/learning-ebpf-maps-ring-buffers-and-output-4300</link>
      <guid>https://forem.com/madmaxx/learning-ebpf-maps-ring-buffers-and-output-4300</guid>
      <description>&lt;p&gt;I set the stage for learning &lt;a href="https://rnemet.dev/posts/ebpf/learn-ebpf" rel="noopener noreferrer"&gt;eBPF&lt;/a&gt;. As mentioned in the previous post, eBPF is a technology that allows us to run code in the kernel.&lt;br&gt;
This is a compelling technology, but it comes with a few limitations. One of them is that we can't use the standard output to print messages. At least not directly. Let's explore how we can do this.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why can't I use the standard input/output?
&lt;/h2&gt;

&lt;p&gt;Let's look at this picture &lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfd70qnoi0ptp0yik0bq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfd70qnoi0ptp0yik0bq.jpg" alt="ebpf-workflow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The eBPF programs are executed in the kernel. The kernel is the core of the operating system. Other processes exist there that ensure the smooth operation of the system. To make this possible, the kernel is running in the privileged mode.&lt;/p&gt;

&lt;p&gt;Programs run by the user are executed in the user space. The user space is running in the unprivileged mode. When the user space program wants to communicate with the kernel, it needs to use the system calls. The system calls are the interface between the user space and the kernel. The system calls are the only way to communicate with the kernel. The kernel is not exposing any other interface to the user space.&lt;/p&gt;

&lt;p&gt;Because of that, we need to have the correct permissions for injecting, executing, and reading the eBPF programs. You must already notice that when you are running the simple &lt;a href="https://github.com/robert-nemet/learn-ebpf/blob/main/intro/hello.py" rel="noopener noreferrer"&gt;hello.py&lt;/a&gt; script, you need to have root permissions. The root permissions have enough privileges&lt;br&gt;
to communicate with the kernel. In this case, load the eBPF program in the kernel, attach it to the events stream, and execute it.&lt;/p&gt;

&lt;p&gt;This is an answer to why we can't use the standard output directly. The standard output is a user space concept. But we can communicate with eBPF programs using the system data structures and calls. They are designed to allow communication between the user space and the kernel, and other eBPF programs can use them to exchange data. Let's scratch the surface of this topic.&lt;/p&gt;
&lt;h2&gt;
  
  
  Simple output(bpf_trace_printk)
&lt;/h2&gt;

&lt;p&gt;Let's look at this BCC example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/python3
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bcc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BPF&lt;/span&gt;

&lt;span class="n"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
#include &amp;lt;linux/sched.h&amp;gt;

int hello(void *ctx) {

    int pid = bpf_get_current_pid_tgid() &amp;gt;&amp;gt; 32;
    int uid = bpf_get_current_uid_gid() &amp;amp; 0xFFFFFFFF;
    char command[TASK_COMM_LEN];
    bpf_get_current_comm(command, sizeof(command));

    bpf_trace_printk(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uid = %d, pid = %d, comm %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, uid, pid, command);

    return 0;
}
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BPF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-Wno-macro-redefined&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;syscall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_syscall_fnname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;execve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach_kprobe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trace_print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple program prints the uid, pid, and the command name of the process executing the &lt;code&gt;execve&lt;/code&gt; system call. I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#4-bpf_get_current_pid_tgid" rel="noopener noreferrer"&gt;bpf_get_current_pid_tgid&lt;/a&gt; function to get the pid(process id) of the
process&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#5-bpf_get_current_uid_gid" rel="noopener noreferrer"&gt;bpf_get_current_uid_gid&lt;/a&gt; function to get the uid(user id) of the
process&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#6-bpf_get_current_comm" rel="noopener noreferrer"&gt;bpf_get_current_comm&lt;/a&gt; function to get the command name of the process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#1-bpf_trace_printk" rel="noopener noreferrer"&gt;bpf_trace_printk&lt;/a&gt; is a function that allows us to print messages from the eBPF program. The messages are sent to the predefined pseudo-file on location &lt;code&gt;/sys/kernel/debug/tracing/trace_pipe&lt;/code&gt;. So, &lt;code&gt;bpf_trace_printk&lt;/code&gt; helper function sends my messages to the &lt;code&gt;trace_pipe&lt;/code&gt; file. The &lt;code&gt;trace_pipe&lt;/code&gt; file is a special file that is used by the kernel to send messages to the user space. Then I can read them with the &lt;code&gt;trace_print()&lt;/code&gt; function or with the &lt;code&gt;cat&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/0Z6EbGoyZNf5xUxeqECbm4UCq" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fasciinema.org%2Fa%2F0Z6EbGoyZNf5xUxeqECbm4UCq.svg" alt="asciicast"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above recording, you'll notice whenever I execute the &lt;code&gt;execve&lt;/code&gt; system call, the eBPF program prints the message on the top third of the screen. The messages are in the &lt;code&gt;trace_pipe&lt;/code&gt; file, too. Notice the middle part of the screen where &lt;code&gt;cat /sys/kernel/debug/tracing/trace_pipe&lt;/code&gt; is executed. At the bottom, I run simple commands like the regular user and root. Notice the difference in the messages. The messages that are printed by the root user have the uid 0. Also, notice that opening a new shell session create several message output. There are many messages because several actions are done when opening a new shell.&lt;/p&gt;

&lt;p&gt;While working &lt;code&gt;bpf_trace_printk&lt;/code&gt; is easy, it has some limitations. For example, if you have multiple eBPF programs that are printing messages, all the messages will be mixed in the &lt;code&gt;trace_pipe&lt;/code&gt; file. It is hard to distinguish which message is coming from which program. Also, the &lt;code&gt;trace_pipe&lt;/code&gt; file is a special file designed for debugging purposes. It is not intended for production use. So, we must find a better way to communicate with the user space.&lt;/p&gt;

&lt;p&gt;I find it helpful to use &lt;code&gt;bpf_trace_printk&lt;/code&gt; for debugging purposes. It is a quick way to print messages from the eBPF program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maps
&lt;/h2&gt;

&lt;p&gt;Maps as data structures are used to store data. When it comes to eBPF, maps are data structures that are used to exchange data between the user space and the kernel.&lt;br&gt;
Also, maps are used to exchange data between eBPF programs. In general, maps are key-value stores. But still, different types of maps exist. The reason is that some of them are optimized for different use cases. At the same time, other eBPF maps hold information about specific object types. For example, there is the &lt;code&gt;BPF_MAP_TYPE_QUEUE&lt;/code&gt; map, which is optimized as a FIFO(first in, first out) queue, and &lt;code&gt;BPF_MAP_TYPE_STACK&lt;/code&gt; which provides a LIFO(last in, first out) stack. Check &lt;a href="https://docs.kernel.org/bpf/map_queue_stack.html" rel="noopener noreferrer"&gt;linux docs&lt;/a&gt;&lt;br&gt;
on them for more information.&lt;/p&gt;

&lt;p&gt;Or maps used to hold information about &lt;a href="https://docs.kernel.org/bpf/map_devmap.html" rel="noopener noreferrer"&gt;network devices&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's check this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/python3  
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bcc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BPF&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;

&lt;span class="n"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
struct data_t {
   u64 counter;
   int pid;
   char command[16];
};

BPF_HASH(counter_table, u64, struct data_t);

int hello(void *ctx) {
   struct data_t zero = {};
   struct data_t *val;


   u64 uid = bpf_get_current_uid_gid() &amp;amp; 0xFFFFFFFF;
   int pid = bpf_get_current_pid_tgid() &amp;gt;&amp;gt; 32;

   val = counter_table.lookup_or_try_init(&amp;amp;uid, &amp;amp;zero);
   if (val) {
      val-&amp;gt;counter++;
      val-&amp;gt;pid = pid;
      bpf_get_current_comm(&amp;amp;val-&amp;gt;command, sizeof(val-&amp;gt;command));
   }

   return 0;
}
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BPF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-Wno-macro-redefined&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;syscall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_syscall_fnname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;execve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach_kprobe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;old_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="nf"&gt;sleep&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="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
   &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;counter_table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
      &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ID &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: cnt: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; pid: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; comm: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;old_s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="n"&gt;old_s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above example is similar to the previous one. But this time, I'm using the map to store information about the processes. I use the &lt;code&gt;BPF_HASH&lt;/code&gt; map to create the map&lt;br&gt;
&lt;code&gt;counter_table&lt;/code&gt;. I use the uid of the process for a key, and for value, I use the &lt;code&gt;struct data_t&lt;/code&gt; structure. The &lt;code&gt;struct data_t&lt;/code&gt; structure holds the counter for all commands the user executed, pid, and the command name of the last process run by a user.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;hello&lt;/code&gt; function is attached to the &lt;code&gt;execve&lt;/code&gt; system call. When the &lt;code&gt;execve&lt;/code&gt; system call is executed, I check if the user is already in the map. If not, I add the user to the map. Suppose the user is already in the map. In that case, I increment the counter and update the pid and the command name of the last process executed by the user.&lt;/p&gt;

&lt;p&gt;Later, in Python script, I read the map and print the information about the users. I use the &lt;code&gt;items()&lt;/code&gt; function to iterate over the map. The &lt;code&gt;items()&lt;/code&gt; function returns the key and value for each entry in the map. I use the &lt;code&gt;value&lt;/code&gt; to get the &lt;code&gt;struct data_t&lt;/code&gt; structure. Then, I print the information about the user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/LL95jvykc7z5YWklFVv8KulnP" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fasciinema.org%2Fa%2FLL95jvykc7z5YWklFVv8KulnP.svg" alt="asciicast"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Ring buffers
&lt;/h2&gt;

&lt;p&gt;Like maps, ring buffers are data structures that exchange data between the user space and the kernel. Look at the picture:&lt;/p&gt;

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

&lt;p&gt;In short, ring buffers are circular buffers. They have two pointers: one for reading and one for writing. The pointers are moving in the same direction. If the read pointer catches the write pointer, the buffer is empty. If the write pointer catches the read pointer, the buffer is full. Then, the next element to be written will be dropped.&lt;/p&gt;

&lt;p&gt;There are two types of ring buffers &lt;a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#2-bpf_perf_output" rel="noopener noreferrer"&gt;BPF_PERF_OUTPUT&lt;/a&gt; and &lt;a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#5-bpf_ringbuf_output" rel="noopener noreferrer"&gt;BPF_RINGBUF_OUTPUT&lt;/a&gt;. The &lt;code&gt;BPF_RINGBUF_OUTPUT&lt;/code&gt; is more advanced than &lt;code&gt;BPF_PERF_OUTPUT&lt;/code&gt;. I will not go into the details about the differences between them &lt;a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#5-bpf_ringbuf_output" rel="noopener noreferrer"&gt;please check the docs&lt;/a&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/python3  
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bcc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BPF&lt;/span&gt;

&lt;span class="n"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
BPF_PERF_OUTPUT(counter_table); 

struct data_t {     
   int pid;
   int uid;
   char command[16];
};


int hello(void *ctx) {
   struct data_t data = {}; 

   data.pid = bpf_get_current_pid_tgid() &amp;gt;&amp;gt; 32;
   data.uid = bpf_get_current_uid_gid() &amp;amp; 0xFFFFFFFF;

   bpf_get_current_comm(&amp;amp;data.command, sizeof(data.command));

   counter_table.perf_submit(ctx, &amp;amp;data, sizeof(data)); 

   return 0;
}
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BPF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-Wno-macro-redefined&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; 
&lt;span class="n"&gt;syscall&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_syscall_fnname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;execve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attach_kprobe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  
   &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;counter_table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;counter_table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;open_perf_buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;print_event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   
   &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_buffer_poll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time difference is when reading from the ring buffer, I'm passing the callback &lt;code&gt;print_event&lt;/code&gt;. The &lt;code&gt;print_event&lt;/code&gt; callback is called when the data is available in the ring buffer. It has to have three arguments: &lt;code&gt;cpu&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, and &lt;code&gt;size&lt;/code&gt;. The &lt;code&gt;cpu&lt;/code&gt; argument is the cpu number on which the event was generated. The &lt;code&gt;data&lt;/code&gt; argument is the data that is read from the ring buffer. The &lt;code&gt;size&lt;/code&gt; argument is the size of the data read from the ring buffer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;So, even if eBPF has no direct access to the standard output, there are ways to exchange data between the user space and the kernel. Plus, given data structures, maps are usually optimized for the specific use case. That should make your life easier.  &lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://elixir.bootlin.com/linux/v6.4.3/source/include/uapi/linux/bpf.h#L905" rel="noopener noreferrer"&gt;linux/bpf.h&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/bpf/maps.html" rel="noopener noreferrer"&gt;kernel docs: eBPF maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#maps" rel="noopener noreferrer"&gt;BCC reference guide: Maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md" rel="noopener noreferrer"&gt;BCC reference guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ebpf</category>
      <category>tutorial</category>
      <category>python</category>
      <category>linux</category>
    </item>
    <item>
      <title>Learning eBPF: Setting up the environment</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Tue, 09 May 2023 07:18:18 +0000</pubDate>
      <link>https://forem.com/madmaxx/learning-ebpf-setting-up-the-environment-2gb4</link>
      <guid>https://forem.com/madmaxx/learning-ebpf-setting-up-the-environment-2gb4</guid>
      <description>&lt;p&gt;For a while, I've been following stuff around eBPF, and it is very promising. What I just wrote is an understatement. At first glance, eBPF is bringing many new possibilities to our toolbox. You can start with performance profiling, tracing, security, networking, etc. But let's start from the beginning.&lt;/p&gt;

&lt;p&gt;By the way, I'm doing this on OSX. For eBPF, you need Linux kernel 4.1 or newer. So, I'll be running some VMs. This setup should be doable on Linux too. Code is available &lt;a href="https://github.com/robert-nemet/learn-ebpf"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is eBPF?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A lit bit of history
&lt;/h3&gt;

&lt;p&gt;A long time ago, in the last century (1992), Steven McCanne and Van Jacobson wrote the paper &lt;a href="https://www.tcpdump.org/papers/bpf-usenix93.pdf"&gt;The BSD Packet Filter&lt;/a&gt;. In short, the goal was to tap on a network interface and filter packets. The result is the in-kernel virtual machine that is used to filter packets. This virtual machine is called Berkeley Packet Filter (BPF). It is not only used for filtering packets but also for tracing, performance profiling, security, etc. &lt;/p&gt;

&lt;p&gt;In 2014, Alexei Starovoitov and Daniel Borkmann started to work on extending BPF. That is how extended BPF (eBPF) was born. eBPF is not only used for filtering packets but as mentioned, eBPF is used for tracing, performance profiling, security, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  eBPF virtual machine
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J0_x7sk_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjcgl2amf5jheid7e4ke.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J0_x7sk_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjcgl2amf5jheid7e4ke.jpg" alt="eBPF overview" width="800" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;EBPF program compiles to eBPF bytecode. This bytecode is loaded into the kernel, verified, and then executed by eBPF virtual machine. It uses different maps to communicate with the kernel, store data, and expose data to user space. Above is how eBPF works, in short. &lt;/p&gt;

&lt;p&gt;eBPF program is event-driven. It is attached to some hook and executed when that hook is triggered. There are many different hooks that are used, like internal system calls, functions entry and exit, network events, etc.&lt;/p&gt;

&lt;p&gt;We write eBPF programs in pseudo-C code and then use LLVM to compile it eBPF bytecode. Or we use eBPF programs with projects like Cilium, bcc, and bpftrace. They are abstractions on the top of eBPF and make our life easier. After the program is compiled to eBPF bytecode, it is loaded into the kernel and verified. If everything is ok, eBPF bytecode is translated to native code by the JIT compiler and executed.&lt;/p&gt;

&lt;p&gt;In the above image, there are eBPF maps. The eBPF programs use them to collect, store and share data. There are different types of eBPF maps, like hash table, array, ring buffer, etc.&lt;/p&gt;

&lt;p&gt;Helper functions, tail, and function calls are not on the image. Helper functions are used to access kernel functions. Tail calls are used to call other eBPF programs. Function calls are used to call other functions inside the eBPF program.&lt;/p&gt;

&lt;p&gt;Enough theory. Let's start with some practice...&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the environment (OSX)
&lt;/h2&gt;

&lt;p&gt;I'm doing this on OSX. But, even if I do this on a Linux box, I still use VMs. Why? Because I would like to have a clean environment and not mess up my host.&lt;/p&gt;

&lt;p&gt;First, I set up a small Git project for messing stuff. I'll be using it for all my experiments. All code will be in this repo. And I'll be using VS Code or Community Edition of IntelliJ IDEA.&lt;/p&gt;

&lt;p&gt;I aim to set up VM(s) to run code and IDE on my host. I should be able to easily share files and run code on VM(s).&lt;/p&gt;

&lt;h3&gt;
  
  
  Vagrant
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.hashicorp.com/vagrant"&gt;Vagrant&lt;/a&gt; would be a CLI tool for managing VMs. But under the hood, it uses &lt;a href="https://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt;. So, you can use Vagrant and VirtualBox. I'm not VirtualBox fun, but I'll give it a try.&lt;/p&gt;

&lt;p&gt;I want to use BCC (BPF Compiler Collection) on the first try. BCC is a toolkit for creating efficient kernel tracing and manipulation programs. It is based on eBPF. It is written in Python. So, my &lt;em&gt;Vagrantfile&lt;/em&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# -*- mode: ruby -*-&lt;/span&gt;
&lt;span class="c1"&gt;# vi: set ft=ruby :&lt;/span&gt;

&lt;span class="vg"&gt;$script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SCRIPT&lt;/span&gt;&lt;span class="sh"&gt;
  echo "Provisioning..."
  sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD
  echo "deb https://repo.iovisor.org/apt/$(lsb_release -cs) $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/iovisor.list
  sudo apt-get update
  sudo apt-get -y install bcc-tools libbcc-examples linux-headers-$(uname -r)
&lt;/span&gt;&lt;span class="no"&gt;SCRIPT&lt;/span&gt;

&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu/bionic64"&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="vg"&gt;$script&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run &lt;code&gt;vagrant up&lt;/code&gt; you should get Ubuntu 18.04 LTS VM with BCC installed. You can check it with &lt;code&gt;vagrant ssh&lt;/code&gt; and then &lt;code&gt;dpkg -l | grep bcc&lt;/code&gt;. You should get something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ii  bcc-tools                        0.10.0-1                            all          Command line tools &lt;span class="k"&gt;for &lt;/span&gt;BPF Compiler Collection &lt;span class="o"&gt;(&lt;/span&gt;BCC&lt;span class="o"&gt;)&lt;/span&gt;
ii  libbcc                           0.10.0-1                            all          Shared Library &lt;span class="k"&gt;for &lt;/span&gt;BPF Compiler Collection &lt;span class="o"&gt;(&lt;/span&gt;BCC&lt;span class="o"&gt;)&lt;/span&gt;
ii  libbcc-examples                  0.10.0-1                            amd64        Examples &lt;span class="k"&gt;for &lt;/span&gt;BPF Compiler Collection &lt;span class="o"&gt;(&lt;/span&gt;BCC&lt;span class="o"&gt;)&lt;/span&gt;
ii  libcc1-0:amd64                   8.4.0-1ubuntu1~18.04                amd64        GCC cc1 plugin &lt;span class="k"&gt;for &lt;/span&gt;GDB
ii  python-bcc                       0.10.0-1                            all          Python wrappers &lt;span class="k"&gt;for &lt;/span&gt;BPF Compiler Collection &lt;span class="o"&gt;(&lt;/span&gt;BCC&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if you go to &lt;code&gt;/vagrant&lt;/code&gt; folder, you'll see your host folder with Vagrantfile. There I keep my files as well. I have &lt;code&gt;hello.py&lt;/code&gt; as &lt;code&gt;Hello World&lt;/code&gt; example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/583603"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zPk1CCzk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://asciinema.org/a/583603.svg" alt="asciicast" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well. I want to skip VirtualBox, and I would like to use the newer Ubuntu. So, I decided to try &lt;a href="https://lima-vm.io/"&gt;Lima&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Lima
&lt;/h3&gt;

&lt;p&gt;According to docs, it is best suited to run on OSX. It uses &lt;a href="https://developer.apple.com/documentation/hypervisor"&gt;Hypervisor.framework&lt;/a&gt; to run VMs. To install it, you can use&lt;br&gt;
&lt;code&gt;brew install lima&lt;/code&gt; or see &lt;a href="https://lima-vm.io/#getting-started"&gt;Getting started&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a start, I'm going with this config:&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;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img"&lt;/span&gt;
  &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x86_64"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img"&lt;/span&gt;
  &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aarch64"&lt;/span&gt;

&lt;span class="na"&gt;mounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp/lima"&lt;/span&gt;
  &lt;span class="na"&gt;writable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;provision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;apt-get update&lt;/span&gt;
    &lt;span class="s"&gt;apt-get install -y apt-transport-https ca-certificates curl clang llvm jq&lt;/span&gt;
    &lt;span class="s"&gt;apt-get install -y libelf-dev libpcap-dev libbfd-dev binutils-dev build-essential make &lt;/span&gt;
    &lt;span class="s"&gt;apt-get install -y linux-tools-common linux-tools-5.15.0-41-generic bpfcc-tools&lt;/span&gt;
    &lt;span class="s"&gt;apt-get install -y python3-pip &lt;/span&gt;
    &lt;span class="s"&gt;rm -rf /usr/local/go &amp;amp;&amp;amp; tar -C /usr/local -xzf go1.20.3.linux-amd64.tar.gz&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just run &lt;code&gt;limactl start --name=ubuntu ubuntu-lst-lima.yml&lt;/code&gt; and choose &lt;code&gt;Proceed with the current configuration&lt;/code&gt;. When it is done, you can run &lt;code&gt;limactl shell ubuntu&lt;/code&gt; to get a shell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/583606"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HzQsKtrQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://asciinema.org/a/583606.svg" alt="asciicast" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Lima for bpftrace
&lt;/h3&gt;

&lt;p&gt;So, in both previous setups, I could run the &lt;code&gt;hello.py&lt;/code&gt; example. But I would also like to run &lt;a href="https://github.com/iovisor/bpftrace"&gt;bpftrace&lt;/a&gt;. For it, I'm creating a separate config:&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;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;# Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img"&lt;/span&gt;
  &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x86_64"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img"&lt;/span&gt;
  &lt;span class="na"&gt;arch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aarch64"&lt;/span&gt;

&lt;span class="na"&gt;mounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp/lima"&lt;/span&gt;
  &lt;span class="na"&gt;writable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;provision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;system&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;apt-get update&lt;/span&gt;
    &lt;span class="s"&gt;echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse deb http://ddebs.ubuntu.com $(lsb_release -cs)-proposed main restricted universe multiverse" | tee -a /etc/apt/sources.list.d/ddebs.list&lt;/span&gt;
    &lt;span class="s"&gt;sudo apt install ubuntu-dbgsym-keyring&lt;/span&gt;
    &lt;span class="s"&gt;sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F2EDC64DC5AEE1F6B9C621F0C8CAB6595FDFF622&lt;/span&gt;
    &lt;span class="s"&gt;apt-get update&lt;/span&gt;
    &lt;span class="s"&gt;apt-get install -y bpftrace &lt;/span&gt;
    &lt;span class="s"&gt;apt-get install -y bpftrace-dbgsym libbpfcc-dbgsym&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create VM with &lt;code&gt;limactl start --name=bpftrace bpftrace.yml&lt;/code&gt; and run &lt;code&gt;limactl shell bpftrace&lt;/code&gt; to get a shell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/583608"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CMKWMACI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://asciinema.org/a/583608.svg" alt="asciicast" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I'm inviting you to join me on this journey. I set up two VMs with Ubuntu 18.04 LTS and Ubuntu 22.04 LTS. I was able to run &lt;code&gt;hello.py&lt;/code&gt; and &lt;code&gt;bpftrace&lt;/code&gt; examples. IMHO, this is good enough for a start. For now, I have all I need to start learning eBPF. If you have any suggestions or comments, please, let me know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Berkeley_Packet_Filter"&gt;Wiki Berkeley Packet Filter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tcpdump.org/papers/bpf-usenix93.pdf"&gt;The BSD Packet Filter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/EBPF"&gt;Wiki eBPF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ebpf.io/"&gt;eBPF&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/iovisor/bcc"&gt;BPF Compiler Collection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.vagrantup.com/"&gt;Vagrant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lima-vm.io/"&gt;Lima&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ebpf</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>New Docker Goodies: Init and Watch</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Tue, 02 May 2023 11:19:44 +0000</pubDate>
      <link>https://forem.com/madmaxx/new-docker-goodies-init-and-watch-ph1</link>
      <guid>https://forem.com/madmaxx/new-docker-goodies-init-and-watch-ph1</guid>
      <description>&lt;p&gt;Recently Docker brought some new stuff that I found very useful for developers. They are still in the experimental phase but are already very useful. &lt;/p&gt;

&lt;p&gt;Try them and give your feedback to the Docker team. The code I'm using for this is &lt;a href="https://github.com/robert-nemet/docker-experiments" rel="noopener noreferrer"&gt;available here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's start...&lt;/p&gt;

&lt;h2&gt;
  
  
  Init
&lt;/h2&gt;

&lt;p&gt;Writing a &lt;em&gt;Dockerfile&lt;/em&gt; when starting with a new application is tedious. Why? Because we are repeating most of the stuff over and over again. So, in the end, we reach to copy/paste solution. Of course, copy/paste needs to be modified to fit an application's needs. I do not say that copy/paste is terrible, but with it, we miss new stuff that Docker brings to us. Even seasoned developers miss stuff. If we do not do it every day, we forget. Because of that, the Docker team brought us a new command called &lt;code&gt;init&lt;/code&gt;. The &lt;code&gt;init&lt;/code&gt; is not new but is still in beta. This time with more improvements.&lt;/p&gt;

&lt;p&gt;It is a straightforward command that will create a Dockerfile for us. It asks a few questions, and based on our answers, it will create a Dockerfile. Let us see how it works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/581956" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fasciinema.org%2Fa%2F581956.svg" alt="asciicast"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is effortless to use. Initially, when the Docker team introduced it, only Go was supported. With the latest release(docker desktop 4.19.0 and docker engine v23.0.5)&lt;br&gt;
&lt;code&gt;docker init&lt;/code&gt; supports NodeJS and Python, too. I'm sure that they will add more languages in the future.&lt;/p&gt;

&lt;p&gt;If you inspect the Dockerfile, you will see that it tries to be simple but also includes all the best practices when writing a Dockerfile. It will try to use cache and multi-stage builds. You can go with generated Dockerfile as is. I prefer to modify it a bit. I suggest you compare my Dockerfile&lt;br&gt;
version with generated one. I like Google's distroless images. In my example, I'm also serving static files, so I need to add them to Dockerfile.&lt;/p&gt;
&lt;h2&gt;
  
  
  Watch
&lt;/h2&gt;

&lt;p&gt;If you look at the &lt;code&gt;compose.yaml&lt;/code&gt; file, you will see nothing exciting. Nothing new or fancy. But there is something new.&lt;/p&gt;

&lt;p&gt;When you are working with &lt;code&gt;docker compose&lt;/code&gt; and want to run your changes, you need to rebuild the image. So, every time you do something like this:&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; &amp;lt;compose file&amp;gt; up &lt;span class="nt"&gt;--detach&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt; &amp;lt;service name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what if you do not need to do it anymore? What if &lt;code&gt;docker compose&lt;/code&gt; can do it for you? Well, it can. And it can do things to rebuild your app only when needed or to update static files. In my example, I have a Go server that serves one static file. When I change the code, I need to rebuild the image. But when I change static files, I want to be updated. &lt;/p&gt;

&lt;p&gt;See:&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;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&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;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;final&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;8080:8080&lt;/span&gt;
    &lt;span class="na"&gt;x-develop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./go.mod&lt;/span&gt;
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rebuild&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./main.go&lt;/span&gt;
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rebuild&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./static/&lt;/span&gt;
        &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sync&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/app/static/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The section under &lt;code&gt;x-develop&lt;/code&gt; with the name &lt;code&gt;watch&lt;/code&gt; does all the magic. That is an experimental feature. It has two &lt;code&gt;actions&lt;/code&gt; that can be used: &lt;code&gt;rebuild&lt;/code&gt; and &lt;code&gt;sync&lt;/code&gt;. The &lt;code&gt;rebuild&lt;/code&gt; will rebuild the image, and the &lt;code&gt;sync&lt;/code&gt; will sync files from the host to the container.&lt;/p&gt;

&lt;p&gt;In the example above, I instruct &lt;code&gt;docker compose&lt;/code&gt; to watch for the changes in the &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;main.go&lt;/code&gt; files and rebuild the image when they change. While if there are any changes in a directory with the name &lt;code&gt;static&lt;/code&gt;(notice the trailing slash), sync them to the container. The &lt;code&gt;target&lt;/code&gt; specifies where to sync files in the container. In my case, I want to sync them to the &lt;code&gt;/app/static/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;As this feature is experimental, it will not work if you just run &lt;code&gt;docker compose up -d&lt;/code&gt;. First, run &lt;code&gt;docker compose up -d&lt;/code&gt;, and run &lt;code&gt;docker compose alpha watch&lt;/code&gt; when all containers are up and running. That will start watching for changes. If you want to stop watching, press &lt;code&gt;Ctrl+C&lt;/code&gt;. Let's see this in action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://asciinema.org/a/glvEsLPFcuSOHVU7Htjiad1vn" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fasciinema.org%2Fa%2FglvEsLPFcuSOHVU7Htjiad1vn.svg" alt="asciicast"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Initially, I built and ran a container with &lt;code&gt;docker compose up -d&lt;/code&gt;. Then I run &lt;code&gt;docker compose alpha watch,&lt;/code&gt; and instruct &lt;code&gt;docker compose&lt;/code&gt; to watch for changes. When I modify &lt;code&gt;index.html&lt;/code&gt; or &lt;code&gt;main.css&lt;/code&gt;, they are synced with the container. When I modify &lt;code&gt;main.go&lt;/code&gt; or &lt;code&gt;go.mod&lt;/code&gt; the image is rebuilt, and the container is restarted. Try it out yourself.  &lt;/p&gt;

&lt;p&gt;If you find this helpful feature, please leave your &lt;a href="https://github.com/compose-spec/compose-spec/pull/253" rel="noopener noreferrer"&gt;feedback and suggestions here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other noticeable stuff
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://docs.docker.com/desktop/release-notes/" rel="noopener noreferrer"&gt;4.19.0 release&lt;/a&gt;, the Docker engine and CLI are updated to &lt;a href="https://github.com/moby/moby/releases/tag/v23.0.0" rel="noopener noreferrer"&gt;Moby 23.0&lt;/a&gt;.&lt;br&gt;
That brings a lot of new stuff. One of the things that can be confusing on start is that &lt;code&gt;docker build&lt;/code&gt; is now an alias for &lt;code&gt;docker buildx build&lt;/code&gt;. The reason is that Buildx and BuildKit are default builders on Linux and OSX. You will notice differences when building images. You'll see switching blue and white lines in the short demos above. White lines are tasks in progress, while blue ones are completed tasks. As well you'll see that Buildx is trying to run tasks in parallel.&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/reference/commandline/init/" rel="noopener noreferrer"&gt;Docker init&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/compose/file-watch/" rel="noopener noreferrer"&gt;Docker watch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>development</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Is Outsourcing Killing Developers</title>
      <dc:creator>Robert Nemet</dc:creator>
      <pubDate>Mon, 24 Apr 2023 06:30:00 +0000</pubDate>
      <link>https://forem.com/madmaxx/is-outsourcing-killing-developers-58lh</link>
      <guid>https://forem.com/madmaxx/is-outsourcing-killing-developers-58lh</guid>
      <description>&lt;p&gt;I live and work in one of the Balkan countries, Belgrade, Serbia. After the sad '90s, all countries in the region try to catch up with the rest of the world. That led to a boom in IT. A large portion of IT is outsourcing companies. Some companies are domestic, and some are foreign. Also, some companies opened their R&amp;amp;D centers in the region. Small and big as well. There was and still is a large number of startups.&lt;/p&gt;

&lt;p&gt;Today, outsourcing is still a big chunk of IT here. Things are changing, but slowly.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Way of IT
&lt;/h2&gt;

&lt;p&gt;I worked for various companies. From startups to big corporations. From small projects for a handful of people to large ones serving millions of users. On some projects, I had a small role, while on some, my part was noticeable. About two-thirds of those years I spent in startups and outsourcing companies. I think I'm in the middle of my carrier at the moment.&lt;/p&gt;

&lt;p&gt;⚠ &lt;em&gt;Small disclaimer. I do not consider myself an expert. I have some experience. I think. Let's say that I like my work&lt;br&gt;
to speak for me.&lt;/em&gt; ⚠&lt;/p&gt;

&lt;p&gt;These are some principles that I gathered over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Things change over time&lt;/em&gt;, especially in the business. Do not get attached to your code. It is legacy at the moment you write it.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Prioritize&lt;/em&gt;. Everything is doable, but not at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;If you have to work extra time, the plan is wrong, or you need more knowledge.&lt;/em&gt; So, either you get trained, or the plan needs to
be changed&lt;/li&gt;
&lt;li&gt;❗ &lt;strong&gt;Know why you are doing it.&lt;/strong&gt; Be familiar with the business goals.&lt;/li&gt;
&lt;li&gt;❗ &lt;strong&gt;Leave your 💩 at the door.&lt;/strong&gt; Do not bring your problems to work. Do not bring your work problems home.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sorry, but I need to be honest. I'll explain myself at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Startups 🏃 🏃 🏃 🏃
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A startup reflects the founder's personality&lt;/em&gt;, especially at the beginning. One thing is sure. You have to work a lot &lt;strong&gt;chasing MVPs.&lt;/strong&gt; The startups are great for learning about tech and business, people, office politics, sacrifice, etc.&lt;/p&gt;

&lt;p&gt;While a startup grows and matures, the dynamics change. As growth happens, more people join the startup. Then, the startup needs new ways to structure the team, processes, and tools. While it was easy to discuss and do things at the begging, at this moment, this is not the case. More people, more dynamics, all that lead to new processes. Those processes are needed to keep the teams on the same page. That is a moment when the startup starts to mimic the product company but wants to keep the startup flavor. Also, it is the moment when some people begin to leave. Most of the time, it is because of a new dynamic. Some individuals like the chaos and hurray of the startup, but that dies with the growth.&lt;/p&gt;

&lt;p&gt;Suppose you join later when the startup matures and has a solid foundation. In that case, it looks more like working for a product company. Still, there is a lot of work to do regarding the product itself and the processes around it. While it is an excellent place to learn and thrive professionally, it can be very demanding. No matter how mature it is, there is much work to make it stable and sustainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Product Companies 📦 📦 📦 📦
&lt;/h2&gt;

&lt;p&gt;Product companies are the ones that have a product that they sell. They are willing to &lt;em&gt;invest in new tech and people to give them an edge&lt;/em&gt;. These organizations are running a business in the long run.&lt;/p&gt;

&lt;p&gt;For me switching from an outsourcing company to a product company was a significant change. It took me some time to digest the difference and realize what I could and needed to do. It was a time when the company that I joined was transforming technically and organizationally. &lt;strong&gt;But when the company builds a healthy culture, it supports its members. In every sense.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Early on, I got copies of &lt;strong&gt;The Phoenix Project&lt;/strong&gt;. Reading that was an eye-opener. It is frustrating when you read it because you'll realize you already know all those principles. But the trick is to formulate them and act. Trying to apply what I learned from it and its sequel &lt;strong&gt;The Unicorn Project&lt;/strong&gt; and &lt;strong&gt;The DevOps Handbook&lt;/strong&gt;(bible) significantly changed how I work.&lt;/p&gt;

&lt;p&gt;Today, tech changes so fast. Mainly to support the business. That is why teams need the freedom to learn and experiment&lt;br&gt;
to create new opportunities for products. Investing in people is a need if a company wants to grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outsourcing Companies 💦 😰 💦
&lt;/h2&gt;

&lt;p&gt;No matter how fancy they are or how much sweet talk their HR does, they care only about client satisfaction. How will one do in such a company depends on many factors.&lt;/p&gt;

&lt;p&gt;❗ &lt;em&gt;The most important one is the client.&lt;/em&gt; The client is coming with the budget to do a project. An outsourcing company promises to deliver that. If clients already have outsourcing experience, it is a smooth ride. If the client has yet to gain any outsourcing experience, good luck.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Soft skills&lt;/em&gt;, this one is quite important. As you can not choose co-workers and clients, you have to be able to find a way to work with different kinds of people. Communicating with people from different cultures, backgrounds, and education is necessary. Also, you need to be able to convince clients to trust you. Your team needs to trust you, too. And you need to trust your team. That can be hard.&lt;/p&gt;

&lt;p&gt;❗ The process in outsourcing companies has a goal to &lt;em&gt;track hours and what is delivered.&lt;/em&gt; Yes, many of them in daily work use agile methods. But, in the end, what is important is how much you worked and delivered. That is why most outsourcing companies invest heavily in processes and tools around&lt;br&gt;
them. That often leads to unnecessary bureaucracy, like the developer does not start working until the ticket is well defined and has all the details. To push not planned work, a client reports a bug. Etc.&lt;/p&gt;

&lt;p&gt;❗ Once a project is over and the client is happy, the part of the team moves to the next project. The rest stays to support the project. Maybe you can choose to remain in the project and retire 😀 But, in general, you will move to the next project.&lt;/p&gt;

&lt;p&gt;❗ The downside is that outsourcing companies are looking for people with specific skills. So, if you do not have them, you do not get hired or hired but do not get paid how much you wanted. Learning new things is more complicated. You are doing it outside work hours. That is because you are not paid to learn but instead to deliver. So, it takes work to get and keep different skills up to date. It could look like working double shifts.&lt;/p&gt;

&lt;p&gt;❗ Influencing system design as a whole is challenging or impossible. In such teams, roles are strict. So, your job is to do what the ticket says, and that is it. There is someone who is creating tickets and doing system design. Whit such a concept, having groomings, retrospectives, and other agile ceremonies is pointless. Depending on the team, that can lead to a lot of frustration. If people that lead the team are willing to share responsibility, and the rest of the team is ready to accept responsibility, only then agile is justified.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Good, Bad, and Ugly
&lt;/h2&gt;

&lt;p&gt;If I put together what I mentioned above, I can say that outsourcing has its primary goal of making money. That it is. It is a business. You have the skill, clients need it, and you exchange it for cash. Simple. The problem is that you focus on a small tech area and a small set of issues. You can say that&lt;br&gt;
you are becoming a specialist. Chasing to be a full stack, whatever that means. Making clients happy and then moving on is your goal.&lt;/p&gt;

&lt;p&gt;Startups and product companies are different. They have to have an idea. Around that idea, they are building a product. That is a never-ending story. So, to survive, they need to take a risk and experiment. Tech is there to minimize the risk and to support the business. That is why they must invest in people and empower them to learn and experiment. To take responsibility. To be able to make decisions. To be able to fail. To be able to learn from mistakes.&lt;/p&gt;

&lt;p&gt;In the end, we are all people. We can't just sell our time. We need to be proud of what we are doing. That is why we need to understand the goals and be able to influence them.&lt;/p&gt;

&lt;p&gt;Next time you are angry at your boss or colleague, or process, ask yourself why you are doing it. Am I at the right place? If you bring your anger home, you are in the wrong place. No matter how high your salary is, it is not worth it. You need to be in a better place if you can enjoy your work, express yourself, and give and receive feedback. That is not happening in many outsourcing companies. They are just coder factories.&lt;/p&gt;

&lt;p&gt;I do not say that all startups and product companies are fantastic. But eventually, they had to start transforming to survive.&lt;/p&gt;

&lt;p&gt;Who is the good, bad, and ugly? It's up to you to decide.&lt;/p&gt;

&lt;p&gt;I'm sorry if I sound like a preacher. It was not my intention. Thank you for reading, and I hope you enjoyed it. If you have any questions, please ask. I'll be happy to answer.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
