robert@notes: ~ — zsh
➜ robert@notes ~ git:(master) cat ~/.motd
Robert's notes - Notes on Go, Kubernetes, Terraform/OpenTofu, AWS and DevOps culture.
➜ robert@notes ~ git:(master) ls ~
➜ robert@notes ~ git:(master) ls -t posts/ | head -n 5 | xargs head -n 3

State surgery without the CLI: import, moved and removed blocks with for_each

For about eight years, changing what is in the state meant typing terraform state mv and terraform import by hand, one resource at a time, in the right order, hoping nobody else ran an apply in between. Those commands still exist, but you should mostly stop using them. There are three declarative blocks - import, moved, removed - that do the same thing from code, show up in the plan, go through code review, and since they accept for_each they handle fifty resources as easily as one. This is in both OpenTofu and Terraform (since 1.7 on both sides), so unlike my last two posts this is not a “Tofu only” trick - it is a “most people still don’t use it” trick.

read more →

OpenTofu state encryption: encrypt an existing state and rotate the key without downtime

Your state file has your database password in it. Not a hash, not a reference, the password. Also every secret the AWS provider ever returned, every private key you generated with the tls provider, every random_password. S3 bucket encryption does not help here, because anyone who can read the bucket gets the plaintext back from S3. OpenTofu 1.7 added client-side state encryption: the state is encrypted before it leaves your machine and S3 only ever sees ciphertext. This post is the practical part - turning it on for an existing project, and then rotating the key - because the docs explain the blocks but not the sequence.

read more →

GitHub Actions beyond the basics: containers, services, your own runners, dynamic matrices and summaries

Most workflows I see are a list of run: steps on ubuntu-latest, and for most repos that is correct. This post is about the five features you reach for when that stops being enough: running a job inside a container image, starting a database next to your tests, bringing your own runners, building the job matrix at run time, and getting plan output into the PR instead of into a log nobody opens. Each one is a few lines of YAML once you know it exists, and each one took me longer to find than it should have.

read more →

Go modules and packages: how to create a module and how to split an app into packages

Programming GoLang

Every Go project starts the same way: one main.go, it works, it grows, and at some point you have a 1500-line file and the question “how do I split this up” - followed by “and what is the difference between a module and a package again”. This post is the answer I give every time. It is short on theory and long on the three commands and one directory layout you actually need, with links to the official docs for the parts I skip.

read more →

One module block, every region: provider for_each in OpenTofu

Here is a thing I wish somebody had shown me the week OpenTofu 1.9 came out. If you have ever had to deploy the same stack to five AWS regions, you know the ritual: five provider "aws" blocks with five aliases, five copies of the module block differing only in the providers map, and a bad feeling every time a sixth region shows up. In OpenTofu you can put for_each on the provider block itself. One provider block, one module block, regions come from a variable. Terraform still cannot do this.

read more →
➜ robert@notes ~ git:(master) ls -t posts/ | sed -n