Bootstrapping Infrastructure: VCS for TF State
Introduction
I use OpenTofu/Terraform(TF)1 to provision/configure my infrastructure, etc. - mostly because an “Infrastructure As Code”2 approach is a sine qua non for me3.
- If I use an “Infrastructure As Code”/“Everything as Code” approach for all my projects in order to have programmatic control over all the things, that should include my VCS repositories, right?
- TF4 needs to store a State file5. The State file contains information about the current state of the Resources that TF wants to manage in order to determine what needs to be changed.
- In the default/trivial/minimal case, TF will use a local file to store State which works but has limitations; what happens when I want to work with this State from a different machine? Or if there is a team? Should I sneakernet it? No, of course not. And so all these technologies include functionality that supports remote State, and most of them support different kinds of backends for storing these States.
- But these backends are infrastructure. So I should manage them with my IaC approach. So now we see a bootstrapping problem.
But git is a distributed VCS, so how about I6 use git as my backend for TF State? That way I can IaC my IaC; this is my usage of terraform-backend-git from Dee Kryvenko. It is a TF Backend provider that allows TF to store its State files in a git repository. See the the author’s explanation for some reasoning.
What are the goals?
Starting with only an account at a VCS Forge (github/gitlab/forgejo/etc.) and a token to allow programmatic access, I want:
- A git repository that contains/can contain TF State files with a conventional directory structure that makes sense for my TF projects.
- A simple workflow/process so that my TF projects all use the same approach for finding/using their TF State.
Implementation
Each TF project(“configuration”) has a root module, with persisted state.
TF projects have their persistent state stored in a directory structure in the tf-states repository that uses the following pattern:
tf-states
└── Project Group
└── the Project's (primary) TF providerFor example:
tf-states
├── rh
│ └── github
│ └── forgejo
└── swip
├── aws
├── github
└── gitlabUsage
Upon starting7 a new TF project, add a new HCL file, tf-backend-git.hcl8:
git.repository = "git@github.com:robinhutty/tf-states.git"
# git.repository = "https://github.com/robinhutty/tf-states"
git.ref = "main"
# The path to the `state` below should follow the directory structure above
git.state = "rh/github/state.json"Then use terraform-backend-git as a wrapper for terragrunt9/opentofu/terraform:
$ terraform-backend-git git terraform -t $(command -v tofu) init
How to bootstrap?
- Create a local git repo,
<path>/rh/github, that will contain the TF project that manages GitHub for therheffort - at least a root module, but it can call child module(s) if they’re available. Might as well use boilerplate, eh?# for a TF project named `foo` git init echo "# foo" > README.md git add README.md git commit -m "Init foo" git branch -M main git remote add origin git@github.com:robinhutty/foo.git git push -u origin main - Write TF code to provision/configure the set of GitHub repositories and other resources for the
rheffort. - At a minimum, that set should contain a repository for the
tf-states! But to make this non-trivial, I’ll also add another repository for my (child) tf-modules. - Run a TF
init- which will install GitHub provider, etc. - Run a TF
apply- which will provision/configure the GitHub repositories - Add a
tf-backend-git.hclreferring to the newtf-statesrepository and the path to where the State file within thetf-statesrepository should live. - Run a TF
init -migrate-state. - Run a TF
plan- which should show no changes.
create a local git repo,
tf-states, with the directory structure I want to use to store the TF State for my TF projectsadd a readme, commit
use terraform-backend-git to provision (at least) the
tf-statesrepository:$ terraform-backend-git git \ --repository /path/to/tf-states \ --ref main \ --state rhu/tf/github/state.json \ terraform -t $(command -v tofu) init $ <snip> plan $ <snip> applyadd the new (remote/GitHub) repository as an origin and push to it
git remote add origin git@github.com:robinhutty/tf-states.git git push -u origin mainmigrate the state into the newly configured repository
$ terraform-backend-git git \ --repository git@github.com:robinhutty/tf-states.git \ --ref main \ --state rhu/tf/github/state.json \ terraform -t $(command -v tofu) init -migrate-state
I’ve been using Terraform continuously since the v0.3.0 release in 2014 which added Modules(!). Like many of us, I’ve been switching to OpenTofu. I’ve started trying to remember to use “TF” when I’m talking about something that is not specific to either tool. ↩︎
First we talked about “Configuration Management”(CM). I used tools like CFEngine, bcfg2, Chef, Puppet and various less well-known techs (including some homegrown by various employers and/or by myself). Sometime (in the mid 2010s? At least a decade after I was working with CM), the term “Continuous Configuration Management” came along. I prefer “… as Code” because, to me, it emphasizes the intention to have programmatic control to beget reproducibility. ↩︎
I have written countless words on “Why IaC?” - mostly in (internal) email and mostly aimed at explaining to management or newbie techies. But okay, I should (will?) publish something with the benefits clearly explained and advice for mitigating the challenges. The main thrust will be “Reproducibility” because, in my opinion, almost everything flows from there. ↩︎
Other IaC technologies mostly have something similar - see Pulumi State, SaltStack State ↩︎
This is necessary to determine the delta between the (current) reality of your Resources and your desired reality for those Resources (remember, this is Declarative IaC, right? The TF code specifies what you want, not what to do). See the material about TF State from OpenTofu and HashiCorp. ↩︎
Relevant Environment Variables: SSH_PRIVATE_KEY, GIT_USERNAME, GITHUB_TOKEN. Also: about multiple [git] identities ↩︎
When starting a new TF project/module, I like to use boilerplate or cookiecutter to help with consistency. See Tips: OpenTofu/Terraform and my templates repository. ↩︎
This pattern allows lends itself to supporting TACOS software such as terragrunt - which can generate the
tf-backend-git.hclfile. ↩︎