Skip to content

Author: Maks Nemisj

Terraform gives you modules. Terragrunt gives you a map.

A while back I kept running into the same blog post, recycled under different titles: “you don’t need Terragrunt anymore, Terraform modules do everything it did.” Different authors, same argument. Terraform grew for_each on modules, got import blocks, got better state management. So the story goes: Terragrunt was a workaround for a young Terraform, and now that Terraform grew up, you can drop the wrapper and go back to plain HCL.

I’ve watched a handful of companies read that post and act on it. Rip Terragrunt out, migrate everything to “clean” Terraform modules, write a triumphant internal Slack message about it. A year later, some of those same teams are quietly rebuilding pieces of what they threw away, just with worse names and no docs.

Here’s my problem with the argument: it compares the wrong things. Terraform is a provisioning language. It’s very good at describing a pile of resources and reconciling reality against that description. Terragrunt was never trying to be a better version of that. It’s a composition tool: it decides what gets run, in what order, with what config, against which state. Modules answer, “How do I build a VPC?” Terragrunt answers, “How many VPCs do I have, where do they live, and how do I see all of them at once?” Those are different questions. Reading module for_each support as a replacement for Terragrunt is like reading “we added folders to our filesystem” as a replacement for Git.

I want to walk through why, using a real migration I just did. Not a toy example. An actual repo, actual GCP project, actual GitHub org, actual scars.

Read more: Terraform gives you modules. Terragrunt gives you a map.

GIT. Learn by doing: Chapter 4

This chapter is going to be part II of explanations about the merge. The reason why I explain merge in so many details because we will need it to understand how the D from DVCS works in Git. D stands for Distributed, and this is where an understanding of ‘merge’ is of significant importance.

Fast-Forward

In the last chapter, we have seen that doing a merge creates a new commit and brings two history lines into one. But what about a “fast-forward” merge, which doesn’t create a separate commit? What is it, and why do we need it?

Fast-forward merge is not a real merge, but merely a movement of a branch label to a new commit. It’s important to note that fast-forward makes only sense when working with branch labels. Such a merge does not change Git history since it does not create any commits. Additionally, such a merge is not possible to trace back, like who did it, when this happened and why.

Often fast-forward occurs when working with a feature branch, and there are no changes in the master branch. As soon as you try to merge a feature branch back to master, Git will apply a fast-forward merge. It will move the ‘master’ label to the commit where the feature branch is on. Look at these two pictures below, one before the merge and one after.

fast-forward stricture before

After I try to merge the feature branch into master, I get the following:

Fortunately, editors of nowadays, like VisualCode are giving us “advice” on not doing ‘fast-forward’ and instead create a commit.

The option “Create a new commit even if fast-forward is possible” is precisely doing this. Creating a commit even for a fast-forward merge might be very convenient. This gives you the ability to trace back when this merge happened, why, and by whom. Though, to do it or not is a matter of taste.

It might look not essential to know about fast-forward, though, as you will see, this information is also needed when we will start studying remotes and distributed property of the Git.

Read more: GIT. Learn by doing: Chapter 4

GIT. Learn by doing: Chapter 3

Merge commits, not branches

In this chapter, we are going to look closer to the merge process. After you’ve learned, that branch is only a label, time to have a look at what merge is.

Let’s start with some theory and slowly move to practice. First, I want to introduce you to the five rules of the merge:

  • The first rule of the merge is:
    The merge is about merging two commits, not branches
  • The second rule of the merge is:
    The merge is about joining two commits, not branches ( this is not a typo, it’s just very essential )
  • The third rule of the merge is:
    It’s not necessary to merge the last two commits ( those without children ), but any commit can be merged with any commit
  • The fourth rule of the merge is:
    Merging two commits produces a new commit, even when you merge the same commits again
  • The fifth rule of the merge is:
    History of both commits should have diverged and they must have a common parent
Read more: GIT. Learn by doing: Chapter 3

GIT: Learn by doing. Chapter 2

What is a “branch” ?

If you have read the previous two chapters, you should already know that commit is the most fundamental part in Git. Diverged history points (alternate future) makes it possible to create branches. In this chapter I’m going to tell you about the “branches” you knew before – what are those, and why do you need them?

It appears that branches which you see in Git client, are only labels by themselves. It’s a human-readable reference to a commit. It’s like a Domain Name System (DNS). Domain name resolves to an IP address, and the branch resolves to a Git commit.

This brings us the first valuable property of a branch. Your commit can have as many labels as you want, and because of that, labels don’t have a time they’ve been created. Remember the task from the previous chapter? Have a look at the picture below. I’ve just added 5 “branches” to the dangling commit I had, while the branch was created at 28 of march, branches/labels are placed now.

Branches are labels
Branches are labels

The second and most valuable point is that removing a branch doesn’t remove the commit itself. It solely removes the label on that commit. I repeat – it SOLELY removes the label on that commit. Very important! Commit stays in history, and even if you do not see it, you can find it as I’ve already described in the previous chapter.

Enough theory it’s time for another exercise.

Read more: GIT: Learn by doing. Chapter 2

GIT: Learn by doing

This is a series of articles about Git (https://git-scm.com) version control system (VCS). I aim to show you Git from a different perspective, starting with the central part of Git – commit and going further into the branches and remotes. You will see what is in reality underneath “origin”, why the button “Create branch” in Jira/GitHub/Gitlab makes no sense, how to merge unmergeable branches, and much much more.

Most of the articles will include tasks to execute in a terminal. I strongly advise you to do them. This way, you will get a better understanding of the explanations. To simplify the bootstrapping of the tasks, I will include a bash/powershell script, which will prepare a basic repository structure.

I hope, as soon as you finish through all the articles, you will level up your Git skills.

Chapters so far:

Read more: GIT: Learn by doing Read more: GIT: Learn by doing

GIT: Learn by doing. Chapter 1

This is the second article of the series about Git. If you haven’t read the introduction, I strongly advise you to do this since this chapter will operate on the knowledge you’ve received in the intro. This time you will have to do exercises in the terminal of your choice. Most of the practices will have a bootstrap script, which will help you to set up the initial folder structure with dummy repositories and commits.

Branches without branch names

The previous chapter was an extended reading, I know, but it was needed to decouple your mind from the idea that “branch” in Git is the holy grail. Contrariwise, the branch is the last thing you should be thinking when working with Git. Putting ‘git commit’ in the first place makes working with Git a different adventure. To make this a second nature, we are going to practice in the form of small tasks, as I already mentioned before.

Read more: GIT: Learn by doing. Chapter 1

GIT: Learn by doing. Intro

Hi there,

This is an introduction to my series of articles regarding Git. The information included is essential for understanding the other chapters. If you are familiar with it, you can skip to the next chapter.

There is no “spoon”

Probably, you have been working with Git for quite a long time; but for now, I want to ask you to forget everything you know about Git (like creating branches, committing to the branch, merging branches, etc.) You should forget about the “origin” and the fact Git has branches.

Task 1:

Do you remember how your Git history looks like? It is, probably, something like this (see below), where you can see branch names and that “origin” word:

git history with branches
Git history you normally see

Now, imagine the same history, but with the “those” strange numbers below each commit.

Read more: GIT: Learn by doing. Intro

useEffectlessState for data fetching with React hooks

Have you ever been into a situation where you wanted to do something with your state inside useEffect hook but didn’t want to put it into a dependency array?

react-hooks/exhaustive-deps is an excellent guard when working with primitive values, but as soon as you have an object in the state, it might stay on your way.

Let me show you some code, which requires state handling in the way I’ve described above.

type FetchInfo =
    | { state: 'LOADING' }
    | { state: 'LOADED' }
    | { state: 'ERROR'; error: Error; statusCode: number | undefined }
    | { state: 'INITIAL' }

interface StateType {
    fetchInfo: FetchInfo
    input: string | undefined
    value: Value
}

export const VerySpecialComponent: React.FC<{ input: string }> = ({ input }) => {
    const [state, setState] = React.useState<StateType>({
        fetchInfo: { state: 'INITIAL' },
        input: undefined,
        value: undefined,
    })

    React.useEffect(() => {
        if (state.fetchInfo.state === 'LOADING' || state.input === input) {
            return
        }

        setState((state) => ({
            ...state,
            fetchInfo: { state: 'LOADING' },
            input,
        }))

        fetchStuff(input).then((result) => {
            setState((state) => ({
                ...state,
                value: result,
                fetchInfo: { state: 'LOADED' },
            }))
        })
    }, [input, state, setState])

    return <div>{state.value}</div>
}

This code looks quite straightforward, still let me elaborate on it.

Read more: useEffectlessState for data fetching with React hooks