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

Testing an OpenTofu module without an AWS account: tofu test and mock_provider

In July I published a DNS Firewall module and admitted that “tested” meant “applied it once and looked”. That is how most infrastructure modules are tested, and it is why module refactors are scary. OpenTofu has had tofu test since 1.6 and provider mocking since 1.8, which together let you assert what a module does - how many rules it creates, that the action is BLOCK, that the wildcard gets added - without credentials, without a VPC, in a few seconds, in CI. Here is that module with real tests.

read more →

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 →
➜ robert@notes ~ git:(master) ls -t posts/ | sed -n